Почему использование курсоров в SQL Server считается плохой практикой? - PullRequest
57 голосов
/ 12 сентября 2008

Я знал о некоторых причинах производительности еще в SQL 7 дней, но существуют ли такие же проблемы в SQL Server 2005? Если у меня есть набор результатов в хранимой процедуре, на который я хочу действовать индивидуально, курсоры по-прежнему плохой выбор? Если так, то почему?

Ответы [ 11 ]

98 голосов
/ 12 сентября 2008

Потому что курсоры занимают память и создают блокировки.

Что вы действительно делаете, так это пытаетесь заставить технологию, основанную на множестве, использовать функциональность, не основанную на множестве. И, честно говоря, я должен отметить, что курсоры do имеют применение, но они не одобряются, потому что многие люди, которые не привыкли использовать решения на основе множеств, используют курсоры вместо того, чтобы выяснять решение на основе.

Но когда вы открываете курсор, вы в основном загружаете эти строки в память и блокируете их, создавая потенциальные блоки. Затем, перебирая курсор, вы вносите изменения в другие таблицы и сохраняете все память и блокировки курсора открытыми.

Все это может вызвать проблемы с производительностью для других пользователей.

Так что, как правило, курсоры не одобряются. Особенно, если это первое решение, найденное при решении проблемы.

21 голосов
/ 11 июня 2010

Приведенные выше комментарии о том, что SQL является средой на основе множеств, верны. Однако бывают случаи, когда полезны построчные операции. Рассмотрим комбинацию метаданных и динамического sql.

В качестве очень простого примера, скажем, у меня есть 100+ записей в таблице, которые определяют имена таблиц, которые я хочу скопировать / усечь / что угодно. Какой лучше? Жесткое кодирование SQL, чтобы сделать то, что мне нужно? Или перебрать этот набор результатов и использовать динамический SQL (sp_executesql) для выполнения операций?

Невозможно достичь вышеуказанной цели с помощью SQL на основе множеств.

Итак, использовать курсоры или цикл while (псевдокурсоры)?

Курсоры SQL хороши, если вы используете правильные параметры:

INSENSITIVE создаст временную копию вашего набора результатов (избавляя вас от необходимости делать это самостоятельно для своего псевдо-курсора).

READ_ONLY будет следить за тем, чтобы в базовом наборе результатов не было никаких блокировок. Изменения в базовом наборе результатов будут отражены в последующих выборках (так же, как если бы вы получали TOP 1 от вашего псевдо-курсора).

FAST_FORWARD создаст оптимизированный курсор только для чтения вперед и только для чтения.

Прочтите о доступных опциях, прежде чем указывать всем курсорам как зло.

15 голосов
/ 21 июля 2011

Вокруг курсоров есть работа, которую я использую каждый раз, когда она мне нужна.

Я создаю табличную переменную со столбцом идентификаторов.

вставьте в него все данные, с которыми мне нужно работать.

Затем создайте блок while с переменной счетчика и выберите нужные данные из табличной переменной с помощью оператора select, в котором столбец идентификаторов соответствует счетчику.

Таким образом, я ничего не блокирую и использую намного меньше памяти и это безопасно, я ничего не потеряю из-за повреждения памяти или чего-то в этом роде.

А код блока легко увидеть и обработать.

Это простой пример:

DECLARE @TAB TABLE(ID INT IDENTITY, COLUMN1 VARCHAR(10), COLUMN2 VARCHAR(10))

DECLARE @COUNT INT,
        @MAX INT, 
        @CONCAT VARCHAR(MAX), 
        @COLUMN1 VARCHAR(10), 
        @COLUMN2 VARCHAR(10)

SET @COUNT = 1

INSERT INTO @TAB VALUES('TE1S', 'TE21')
INSERT INTO @TAB VALUES('TE1S', 'TE22')
INSERT INTO @TAB VALUES('TE1S', 'TE23')
INSERT INTO @TAB VALUES('TE1S', 'TE24')
INSERT INTO @TAB VALUES('TE1S', 'TE25')

SELECT @MAX = @@IDENTITY

WHILE @COUNT <= @MAX BEGIN
    SELECT @COLUMN1 = COLUMN1, @COLUMN2 = COLUMN2 FROM @TAB WHERE ID = @COUNT

    IF @CONCAT IS NULL BEGIN
        SET @CONCAT = '' 
    END ELSE BEGIN 
        SET @CONCAT = @CONCAT + ',' 
    END

    SET @CONCAT = @CONCAT + @COLUMN1 + @COLUMN2

    SET @COUNT = @COUNT + 1
END

SELECT @CONCAT
10 голосов
/ 12 сентября 2008

Я думаю, что курсоры имеют дурную славу, потому что новички в SQL обнаруживают их и думают: «Эй, цикл for! Я знаю, как их использовать!» а затем они продолжают использовать их для всего.

Если вы используете их для того, для чего они предназначены, я не могу придраться к этому.

9 голосов
/ 12 сентября 2008

SQL - это язык, основанный на множествах - это то, что он делает лучше всего.

Я думаю, что курсоры по-прежнему плохой выбор, если вы недостаточно разбираетесь в них, чтобы оправдать их использование в ограниченных обстоятельствах.

Еще одна причина, по которой я не люблю курсоры, это ясность. Блок курсора настолько уродлив, что его трудно использовать четко и эффективно.

Все, что было сказано, - это некоторые случаи, когда курсор действительно лучше - это просто не те случаи, когда новички хотят их использовать.

4 голосов
/ 19 мая 2011

@ Daniel P -> вам не нужно использовать курсор для этого. Вы можете легко использовать теорию, основанную на множестве, чтобы сделать это. Например: с Sql 2008

DECLARE @commandname NVARCHAR(1000) = '';

SELECT @commandname += 'truncate table ' + tablename + '; ';
FROM tableNames;

EXEC sp_executesql @commandname;

просто сделает то, что вы сказали выше. И вы можете сделать то же самое с Sql 2000, но синтаксис запроса будет другим.

Однако я советую избегать курсоров как можно больше.

Gayam

4 голосов
/ 12 сентября 2008

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

Я бы не назвал это "плохой практикой" использования курсоров, но они потребляют больше ресурсов на сервере (чем эквивалентный подход на основе множеств), и чаще всего они не являются необходимыми. Учитывая это, я бы посоветовал рассмотреть другие варианты, прежде чем прибегать к курсору.

Существует несколько типов курсоров (только вперед, статический, набор клавиш, динамический). Каждый из них имеет различные характеристики производительности и связанные с этим накладные расходы. Убедитесь, что вы используете правильный тип курсора для вашей операции. Только пересылка по умолчанию.

Один аргумент для использования курсора - это когда вам нужно обрабатывать и обновлять отдельные строки, особенно для набора данных, у которого нет хорошего уникального ключа. В этом случае вы можете использовать предложение FOR UPDATE при объявлении курсора и обрабатывать обновления с помощью UPDATE ... WHERE CURRENT OF.

Обратите внимание, что серверные курсоры были популярны (из ODBC и OLE DB), но ADO.NET их не поддерживает, и AFAIK никогда не будет.

3 голосов
/ 12 сентября 2008

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

Даже курсор быстрой пересылки в Sql Server 2005 не может конкурировать с запросами на основе множеств. График снижения производительности часто начинает выглядеть как операция n ^ 2 по сравнению с набором, который имеет тенденцию быть более линейным, поскольку набор данных становится очень большим.

2 голосов
/ 21 июня 2015

Курсоры, как правило, не болезнь, а ее симптом: не используется подход, основанный на множестве (как упоминалось в других ответах).

Непонимание этой проблемы и просто вера в то, что ее устранение может решить «злой» курсор, может усугубить ситуацию.

Например, замена итерации курсора другим итеративным кодом, таким как перемещение данных во временные таблицы или переменные таблиц, для циклического перемещения по строкам следующим образом:

SELECT * FROM @temptable WHERE Id=@counter 

или

SELECT TOP 1 * FROM @temptable WHERE Id>@lastId

Такой подход, как показано в коде другого ответа, значительно ухудшает ситуацию и не устраняет исходную проблему. Это анти-паттерн, который называется программирование культа грузов : не знать, ПОЧЕМУ что-то плохо, и, следовательно, реализовывать что-то худшее, чтобы избежать этого! Недавно я изменил такой код (используя #temptable и без индекса для identity / PK) обратно на курсор, и обновление чуть более 10000 строк заняло всего 1 секунду вместо почти 3 минут. Все еще не хватает подхода, основанного на множестве (будучи меньшим злом), но лучшее, что я мог сделать в этот момент.

Другим признаком этого недостатка понимания может быть то, что я иногда называю «болезнью одного объекта»: приложения баз данных, которые обрабатывают отдельные объекты через слои доступа к данным или объектно-реляционные сопоставители. Обычно код такой:

var items = new List<Item>();
foreach(int oneId in itemIds)
{
    items.Add(dataAccess.GetItemById(oneId);
}

вместо

var items = dataAccess.GetItemsByIds(itemIds);

Первый обычно заполняет базу данных множеством SELECT, по одному обходу в каждом, особенно когда в игру вступают деревья / графики объектов и возникает печально известная проблема SELECT N + 1.

Это прикладная сторона непонимания реляционных баз данных и подхода, основанного на множестве, точно так же, как курсоры при использовании процедурного кода базы данных, такого как T-SQL или PL / SQL!

2 голосов
/ 12 сентября 2008

Курсоры имеют свое место, однако я думаю, что это в основном потому, что они часто используются, когда достаточно одного оператора выбора для обеспечения агрегации и фильтрации результатов.

Избегание курсоров позволяет SQL Server более полно оптимизировать производительность запроса, что очень важно в больших системах.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...