Курсоры в SQL позволяют построчно обходить набор данных и выполнять над каждой записью определённые действия, что похоже на итерацию в программировании. Это удобно для сложной пошаговой логики, невозможной средствами SQL одного запроса (например, шаговые вычисления с изменением внешнего состояния).
Пример курсора:
DECLARE my_cursor CURSOR FOR SELECT id, balance FROM Accounts WHERE isActive = 1; OPEN my_cursor; DECLARE @id INT, @bal DECIMAL(10,2); FETCH NEXT FROM my_cursor INTO @id, @bal; WHILE @@FETCH_STATUS = 0 BEGIN UPDATE Accounts SET balance = @bal * 1.05 WHERE id = @id; FETCH NEXT FROM my_cursor INTO @id, @bal; END CLOSE my_cursor; DEALLOCATE my_cursor;
Преимущества:
Недостатки и подводные камни:
Можно ли использовать курсор внутри триггера? Какие последствия это может иметь для производительности базы данных?
Ответ и пример: Использование курсора внутри триггера возможно, но крайне не рекомендуется — каждый DML-оператор может запускать курсор для каждой затронутой строки, что приводит к лавинообразному росту запросов и блокировок.
CREATE TRIGGER UpdateBalance ON Accounts AFTER INSERT AS DECLARE c CURSOR FOR SELECT id FROM inserted; -- плохо! OPEN c; -- ...
История
Проект: Сводка заказов в ритейле. Была задача подсчитать остатки на складе, если в партии найден брак — последующие партии требуют пересчёта вручную через обновление скидок. Использовали курсор для обхода партий. Позже выяснилось, что при многократных запусках поток блокировался на часы, нагрузка на сервер росла экспоненциально, а lock contention приводил к сбоям.
История
Проект: ERP, миграция данных. В процедур обработки импорта добавили обработку ошибок через курсор. Не учли, что на 5 млн строк курсор работает в 40 раз медленнее, чем аналогичная batch обработка средствами set-based UPDATE + CASE. Из-за медленной миграции сдвинулись дедлайны.
История
Проект: Billing финансовой компании. В триггер на обновление таблицы движения средств добавили курсор, чтобы вычислить новый агрегатный остаток. В продакшне это привело к "STOP THE WORLD" — вставка даже одной записи замедлила работу сервиса из-за запуска вложенного курсора по многим строкам.