Почему использование курсоров в SQL Server считается плохой практикой?

StackOverflow https://stackoverflow.com/questions/58141

Вопрос

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

Это было полезно?

Решение

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

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

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

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

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

Другие советы

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Одним из аргументов в пользу использования курсора является необходимость обработки и обновления отдельных строк, особенно для набора данных, у которого нет хорошего уникального ключа.В этом случае вы можете использовать предложение FOR UPDATE при объявлении курсора и обрабатывать обновления с помощью UPDATE...ГДЕ ТОК.

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

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

DECLARE @commandname NVARCHAR(1000) = '';

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

EXEC sp_executesql @commandname;

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

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

Гаям

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

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

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

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

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

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

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

SELECT * FROM @temptable WHERE Id=@counter 

или

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

Такой подход, как показано в коде другого ответа, значительно ухудшает ситуацию и не решает исходную проблему.Это антипаттерн под названием карго культовое программирование:не зная, ПОЧЕМУ что-то плохо, и поэтому прибегая к чему-то еще худшему, чтобы этого избежать!Недавно я изменил такой код (используя #temptable и отсутствие индекса для идентификатора/PK) обратно на курсор, и обновление чуть более 10 000 строк заняло всего 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!

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

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

Циклы — это не то, для чего предназначена архитектура базы данных или хранилища, и даже в SQL Server 2005 вы не получите производительности, близкой к той, которую вы получаете, если вы перенесете базовый набор данных в специальную программу и выполните цикл в памяти. , используя максимально легкие объекты/структуры данных.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top