ПрограммированиеSQL разработчик

Опишите особенности, преимущества и подводные камни серийной обработки данных с помощью курсоров (CURSOR) в SQL. Когда целесообразно использовать курсоры, а когда стоит избегать их в пользу сетевых (set-based) операций? Приведите пример использования курсора.

Проходите собеседования с ИИ помощником Hintsage

Ответ.

Курсоры в 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;

Преимущества:

  • Позволяют реализовать сложные процедуры, где каждая строка требует отдельной обработки или проверки внешних условий.
  • Удобны для интеграции с устаревшими системами или миграций, где нужен пошаговый контроль.

Недостатки и подводные камни:

  • Курсоры обычно ОЧЕНЬ медленны: обработка идет поштучно, а не сетевым образом;
  • Занимают много ресурсов, могут блокировать таблицы;
  • Не масштабируются для большого объема данных;
  • Лучше переписывать на set-based SQL или использовать пакетную обработку.

Вопрос с подвохом.

Можно ли использовать курсор внутри триггера? Какие последствия это может иметь для производительности базы данных?

Ответ и пример: Использование курсора внутри триггера возможно, но крайне не рекомендуется — каждый 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" — вставка даже одной записи замедлила работу сервиса из-за запуска вложенного курсора по многим строкам.