Курсор базы данных
Курсор базы данных — это управляющая структура, используемая в системах управления базами данных (СУБД) для последовательной обработки строк, возвращаемых запросом. Курсор позволяет программе или хранимой процедуре получить доступ к результату запроса не как к единому набору данных, а как к отдельным записям, которые можно обрабатывать по одной. Это особенно важно в тех случаях, когда необходимо выполнить действия, зависящие от содержимого каждой конкретной строки, или когда результирующий набор слишком велик для единовременной загрузки в память.
Принцип работы и назначение
В основе работы реляционных баз данных лежит теория множеств: оператор SELECT возвращает набор строк (реляцию), который обрабатывается как единое целое. Однако для многих прикладных задач требуется построчная обработка. Курсор предоставляет механизм для итерации по этому набору.
Основные этапы работы с курсором включают:
- Объявление (DECLARE): Создание курсора и связывание его с запросом
SELECT. - Открытие (OPEN): Выполнение запроса и формирование результирующего набора строк, который будет доступен через курсор.
- Выборка (FETCH): Извлечение одной строки из набора и перемещение указателя на следующую строку.
- Закрытие (CLOSE): Освобождение ресурсов, связанных с курсором.
- Освобождение (DEALLOCATE): Полное удаление определения курсора из памяти.
Курсоры широко применяются в хранимых процедурах, функциях и триггерах, а также в клиентских приложениях, работающих с базами данных через API (например, ODBC, JDBC, ADO.NET).
Классификация курсоров
Курсоры классифицируются по нескольким признакам, определяющим их поведение и возможности.
По направлению движения
- Однонаправленные (Forward-only): Позволяют перемещаться только вперёд по набору строк. После выборки строки вернуться к ней без повторного выполнения запроса невозможно. Это самый простой и производительный тип курсора.
- Прокручиваемые (Scrollable): Предоставляют возможность перемещаться по результирующему набору в любом направлении: вперёд, назад, к первой строке, к последней, к произвольной строке (абсолютная позиция). Это даёт большую гибкость, но требует больше ресурсов.
По чувствительности к изменениям
Эта классификация определяет, видит ли курсор изменения, вносимые другими транзакциями в данные после его открытия.
- Статические (Static): Создают снимок данных на момент открытия курсора. Изменения, вносимые другими пользователями (или самой транзакцией, но после открытия), не отображаются. Это самый безопасный, но и самый ресурсоёмкий тип, так как данные копируются во временную таблицу.
- Динамические (Dynamic): Отражают все изменения, вносимые в базовые таблицы во время существования курсора. Видимость изменений зависит от уровня изоляции транзакции. Этот тип наиболее актуален, но требует постоянного контроля блокировок и может быть медленным.
- Управляемые набором ключей (Keyset-driven): Компромиссный вариант. При открытии курсора фиксируется набор уникальных ключей строк, входящих в результирующий набор. При выборке строки курсор считывает актуальные данные по ключу. Таким образом, он видит изменения в неключевых полях, но не видит новые строки, добавленные другими пользователями, а строки, удалённые другими пользователями, становятся недоступными.
По расположению
- Курсоры на стороне сервера (Server-side): Полностью обрабатываются СУБД. Клиент получает только те строки, которые он явно запросил операцией
FETCH. - Курсоры на стороне клиента (Client-side): Весь результирующий набор данных передаётся клиентскому приложению, которое и управляет навигацией по нему. Это снижает нагрузку на сервер, но может потреблять много памяти на стороне клиента.
Примеры реализации в различных СУБД
Синтаксис и возможности курсоров различаются в зависимости от СУБД.
PostgreSQL
В PostgreSQL курсоры используются в основном в хранимых процедурах и анонимных блоках кода (PL/pgSQL). Поддерживаются как однонаправленные, так и прокручиваемые курсоры.
``sql DO $$ DECLARE cur CURSOR FOR SELECT id, name FROM users; rec RECORD; BEGIN OPEN cur; LOOP FETCH cur INTO rec; EXIT WHEN NOT FOUND; -- Обработка строки RAISE NOTICE 'User ID: %, Name: %', rec.id, rec.name; END LOOP; CLOSE cur; END $$; ``
Microsoft SQL Server (T-SQL)
T-SQL предлагает богатый набор опций для объявления курсоров, включая указание типа (статический, динамический, управляемый набором ключей) и возможности прокрутки.
```sql DECLARE @id INT, @name NVARCHAR(100);
DECLARE user_cursor CURSOR FOR SELECT id, name FROM users;
OPEN user_cursor;
FETCH NEXT FROM user_cursor INTO @id, @name;
WHILE @@FETCH_STATUS = 0 BEGIN -- Обработка строки PRINT 'User ID: ' + CAST(@id AS NVARCHAR(10)) + ', Name: ' + @name; FETCH NEXT FROM user_cursor INTO @id, @name; END
CLOSE user_cursor; DEALLOCATE user_cursor; ```
Oracle Database
В Oracle курсоры делятся на явные (объявляемые программистом) и неявные (создаваемые автоматически для операторов DML и SELECT INTO). Также существует тип SYS_REFCURSOR, позволяющий возвращать курсор как результат функции.
``sql DECLARE CURSOR cur IS SELECT id, name FROM users; rec cur%ROWTYPE; BEGIN OPEN cur; LOOP FETCH cur INTO rec; EXIT WHEN cur%NOTFOUND; -- Обработка строки DBMS_OUTPUT.PUT_LINE('User ID: ' || rec.id || ', Name: ' || rec.name); END LOOP; CLOSE cur; END; ``
Преимущества и недостатки
Преимущества
- Построчная обработка: Позволяет выполнять сложные вычисления, логику или вызовы других процедур для каждой строки индивидуально.
- Управление памятью: Обработка больших наборов данных без необходимости загружать их целиком в оперативную память клиента.
- Гибкость навигации: Прокручиваемые курсоры позволяют перемещаться по набору данных в произвольном порядке.
Недостатки
- Низкая производительность: Курсоры, как правило, работают значительно медленнее, чем операции, основанные на множествах (
UPDATE,INSERT,DELETEс подзапросами). Каждый вызовFETCHможет приводить к отдельному дисковому вводу-выводу. - Блокировки: Динамические курсоры могут удерживать блокировки на строки, что снижает параллелизм работы с базой данных.
- Сложность кода: Код с использованием курсоров часто более громоздкий и сложный для понимания и сопровождения, чем эквивалентный код на основе множеств.
Альтернативы курсорам
В большинстве случаев, где применяются курсоры, их можно заменить более эффективными конструкциями, основанными на теории множеств:
- Подзапросы (Subqueries) и CTE (Common Table Expressions): Позволяют выполнить сложную логику в рамках одного оператора SQL.
- Оконные функции (Window Functions): Используются для вычисления агрегатов и ранжирования без группировки строк.
- Рекурсивные CTE: Применяются для обработки иерархических данных (например, дерево категорий).
- Массовые операции (Bulk Operations): Вставка, обновление или удаление сразу множества строк на основе условия.
Использование курсоров рекомендуется только в тех случаях, когда построчная обработка действительно необходима и не может быть реализована средствами декларативного языка SQL, например, при вызове внешних процедур для каждой строки или при сложной логике, зависящей от состояния предыдущих строк.
Источники
- Дейт К. Дж. Введение в системы баз данных. — 8-е изд. — М.: Вильямс, 2005.
- Грофф Дж., Вайнберг П. SQL: Полное руководство. — 3-е изд. — М.: Вильямс, 2014.
- Документация PostgreSQL: Глава 43. PL/pgSQL — Курсоры.
- Документация Microsoft SQL Server: DECLARE CURSOR (Transact-SQL).
- Документация Oracle Database: Database PL/SQL Language Reference — Cursors.
BFOmetr — база данных и аналитика по компаниям России.
На главную BFOmetr →