Почему люди так ненавидят курсоры SQL?

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

Например, был задан один вопрос, как сделать что-то очевидно тривиальное с курсором, и принятый ответ, предложенный с использованием рекурсивного запроса общего табличного выражения (CTE) с рекурсивной настраиваемой функцией, хотя это ограничивает количество строк, которые могут быть обработаны, до 32 (из-за ограничения рекурсивного вызова функции на сервере sql). Это кажется мне ужасным решением проблемы долговечности системы, не говоря уже о огромных усилиях, направленных на то, чтобы избежать использования простого курсора.

В чем причина такой безумной ненависти? Издал ли какой-нибудь «авторитетный авторитет» фетву против курсоров? Неужели в основе курсоров таится какое-то невыразимое зло, которое развращает нравы детей или что-то в этом роде?

Вопрос вики, больше интересует ответ, чем представитель.

Связанная информация:

Курсоры быстрой перемотки SQL Server

РЕДАКТИРОВАТЬ: позвольте мне быть более точным: я понимаю, что курсоры не должны использоваться вместо обычных операций отношения; это и ежу понятно. Чего я не понимаю, так это то, что люди стараются изо всех сил избегать курсоров, как будто у них есть кути или что-то в этом роде, даже когда курсор является более простым и / или более эффективным решением. Меня сбивает с толку иррациональная ненависть, а не очевидная техническая эффективность.


person Community    schedule 13.11.2008    source источник
comment
Я думаю, что ваш Edit говорит обо всем ... В почти всех ситуациях (с которыми я сталкивался) есть способ заменить курсор более производительной ситуацией на основе набора. Вы говорите, что это понятно, но вы понимаете разницу.   -  person StingyJack    schedule 13.11.2008
comment
Мне нравятся теги на этом вопросе!   -  person sep332    schedule 14.11.2008
comment
Часть о том, что пределы рекурсивного CTE равны 32, - ерунда. Предположительно вы думаете о рекурсивных триггерах и максимальном @@NESTLEVEL из 32. Он может быть установлен в запросе с OPTION (MAXRECURSION N) со значением по умолчанию 100 и 0, что означает неограниченный.   -  person Martin Smith    schedule 02.11.2012
comment
@MartinSmith: теперь ограничение по умолчанию составляет 100, а максимальное - 32 КБ sql-server-helper.com/error-messages/msg-310.aspx   -  person Steven A. Lowe    schedule 12.01.2016
comment
Нет, это точно так же, как и во всех версиях SQL Server, поддерживающих рекурсивные CTE. Как говорится в вашей ссылке, когда указан 0, ограничение не применяется.   -  person Martin Smith    schedule 12.01.2016
comment
@MartinSmith: спасибо, моя ошибка - на самом деле две ошибки;) первая заключалась в неправильном прочтении ссылки (я предположил, что ограничение 32K = 'unlimited'), а вторая была неправильной причиной - в приведенном примере ограничение рекурсии 32 было получено из рекурсивная функция, а не CTE. В то время я, вероятно, использовал SQL Server 2000, а может быть, 2008, надеюсь, теперь лучше :). Вопрос отредактирован для уточнения - признателен за исправление!   -  person Steven A. Lowe    schedule 12.01.2016


Ответы (13)


«Накладные расходы» на курсоры - это просто часть API. Курсоры - это то, как части СУБД работают под капотом. Часто CREATE TABLE и INSERT имеют SELECT операторы, и реализация является очевидной реализацией внутреннего курсора.

Использование высокоуровневых «операторов на основе наборов» объединяет результаты курсора в единый набор результатов, что означает меньшее количество операций API вперед и назад.

Курсоры предшествуют современным языкам, которые предоставляют первоклассные коллекции. В старых версиях C, COBOL, Fortran и т. Д. Приходилось обрабатывать строки по одной, потому что не существовало понятия «коллекции», которое можно было бы широко использовать. Java, C #, Python и т. Д. Имеют первоклассные структуры списков для хранения наборов результатов.

Медленная проблема

В некоторых кругах реляционные соединения являются загадкой, и люди будут писать вложенные курсоры, а не простое соединение. Я видел поистине эпические операции вложенного цикла, записанные в виде множества курсоров. Как победить оптимизацию РСУБД. И работает очень медленно.

Простое переписывание SQL для замены вложенных циклов курсора объединениями, а единственный плоский цикл курсора может заставить программы работать в 100 раз быстрее. [Они думали, что я бог оптимизации. Все, что я сделал, это заменил вложенные циклы на соединения. Курсоры все еще используются.]

Эта путаница часто приводит к обвинению в использовании курсоров. Однако проблема не в курсоре, а в неправильном использовании курсора.

Проблема размера

Для действительно эпичных наборов результатов (например, выгрузки таблицы в файл) курсоры необходимы. Операции на основе наборов не могут материализовать действительно большие наборы результатов в виде единой коллекции в памяти.

Альтернативы

Я стараюсь как можно больше использовать слой ORM. Но это преследует две цели. Во-первых, курсоры управляются компонентом ORM. Во-вторых, SQL отделяется от приложения в файле конфигурации. Дело не в том, что курсоры плохие. Дело в том, что кодирование всех этих операций открытия, закрытия и выборки не является дополнительным программированием.

person Community    schedule 13.11.2008
comment
Курсоры - это то, как СУБД работает под капотом. Если вы имеете в виду именно SQL Server, хорошо, я этого не знаю. Но я работал над внутренним устройством нескольких СУБД (и ОРСУБД) (под Stonebraker), и ни одна из них этого не сделала. Например: Ingres внутренне использует то, что составляет результирующие наборы кортежей. - person Richard T; 28.12.2008
comment
@Richard T: Я работаю над вторичной информацией об источнике СУБД; Я поправлю заявление. - person S.Lott; 28.12.2008
comment
Я видел поистине эпические операции вложенного цикла, записанные в виде множества курсоров. Я тоже их вижу. В это трудно поверить. - person RussellH; 30.12.2008

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

И они МЕДЛЕННЫЕ !!!

Из SQLTeam:

Обратите внимание, что курсоры - это САМЫЙ МЕДЛЕННЫЙ способ доступа к данным внутри SQL Server. Его следует использовать только тогда, когда вам действительно нужно получить доступ к одной строке за раз. Единственная причина, по которой я могу это придумать, - это вызов хранимой процедуры для каждой строки. В статье о производительности курсора я обнаружил, что курсоры более чем в тридцать раз медленнее заданного альтернативы на основе.

person Community    schedule 13.11.2008
comment
этой статье 7 лет, как вы думаете, возможно, за это время что-то изменилось? - person Steven A. Lowe; 13.11.2008
comment
Я также считаю, что курсоры действительно медленные, и их следует избегать. Однако, если OP имел в виду вопрос, который, как мне кажется, он был, тогда правильным решением был курсор (потоковая передача записей по одной из-за ограничений памяти). - person rmeador; 13.11.2008
comment
обновленная статья не исправляет измерения относительной скорости, но дает некоторые хорошие оптимизации и альтернативы. Обратите внимание, что в исходной статье говорится, что курсоры в 50 раз быстрее, чем циклы while, что интересно. - person Steven A. Lowe; 13.11.2008
comment
Я лично считаю, что если вам нужен курсор, вы изначально неправильно спроектировали свою базу данных. - person BoltBait; 13.11.2008
comment
@BoltBait: Я лично считаю, что если вы делаете такие общие утверждения, вам не может быть 45 лет :-P - person Steven A. Lowe; 14.11.2008
comment
Курсоры не так уж медленны в Oracle, где вы можете использовать такие предложения, как BULK-COLLECT, для кода, ориентированного на производительность. Хотя было бы неплохо провести тест. - person Camilo Díaz Repka; 14.11.2008
comment
@ Стивен: Да, я старый ... и очень самоуверенный! - person BoltBait; 17.11.2008
comment
@BoltBait: Ребята, слезайте с моей лужайки! - person Steven A. Lowe; 20.11.2008
comment
Я думаю, это процитировал Эдвин Дейкстра: преждевременная оптимизация - корень всех зол ... поэтому иногда я думаю, к черту аргументы в пользу производительности. Используйте наборы, в которых наборы должны применяться, а также в логике, чтобы сделать ваш алгоритм понятным. Но иногда бизнес-логика может быть намного более ясной и адаптируемой при последовательном переходе. Ну это, очевидно, мое мнение. - person Paul; 14.02.2014
comment
@ Пол: это цитата Кнута, и это Эдгар Дейкстра, но я согласен с остальной частью вашего предложения :) - person Steven A. Lowe; 12.01.2016
comment
Забавно, я считаю, что бизнес-логика почти всегда яснее в коде, основанном на наборах. - person HLGEM; 21.08.2017

Выше есть ответ, в котором говорится, что «курсоры - САМЫЙ МЕДЛЕННЫЙ способ доступа к данным внутри SQL Server ... курсоры более чем в тридцать раз медленнее, чем альтернативы, основанные на наборах».

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

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

person Community    schedule 13.11.2008
comment
Похоже на исключение, подтверждающее правило. - person Joel Coehoorn; 13.11.2008
comment
@ [Джоэл Кохорн]: Я никогда не понимал этого высказывания. - person Steven A. Lowe; 13.11.2008
comment
@ [Стивен А. Лоу] phrases.org.uk /meanings/exception-that-proves-the-rule.html понимает исключение как то, что не учитывается, и обратите внимание, что правило здесь похоже на то, что в большинстве ситуаций курсоры плохие. - person David Lay; 13.11.2008
comment
@delm: спасибо за ссылку, теперь фразу еще меньше понимаю! - person Steven A. Lowe; 13.11.2008
comment
@ [Steven A. Lowe] По сути, это говорит о том, что если вы нарушаете правило с помощью подслучая, должно быть общее правило, которое нужно нарушить, следовательно, правило существует. например По ссылке: (Если у нас есть утверждение типа «вход бесплатный по воскресеньям», мы можем разумно предположить, что, как правило, вход платный.) - person Fry; 13.11.2008
comment
@Fry: хорошо, это имеет смысл - так как это применимо здесь? - person Steven A. Lowe; 13.11.2008
comment
Я думаю, он говорит, что при проблемах с блокировкой / ограничениями памяти используйте курсоры. Это означает, что в противном случае вам не следует использовать курсоры. - person SapphireSun; 27.02.2010
comment
Другой пример использования подходящего курсора: support.microsoft.com/kb/973849. В исходной версии использовались методы, основанные на наборах, и были проблемы с блокировкой. - person Moe Sisko; 02.07.2013
comment
И что интересно, тип набора данных, о котором идет речь (данные о состоянии сеанса из веб-приложений), в точности соответствует критерию, который я упомянул в исходном посте - набор данных, в котором операция затрагивает множество строк, но сама таблица получает постоянные производственные чтения. Первоначальная версия этого была бы смертью для сайта с любым значительным объемом трафика. - person davidcl; 16.07.2013

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

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

Очевидно, существуют ситуации, когда курсоры - правильный выбор или, по крайней мере, правильный выбор.

person Community    schedule 13.11.2008

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

person Community    schedule 13.11.2008

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

В Oracle 10 часто используемый неявный курсор

  for x in (select ....) loop
    --do something 
  end loop;

неявно выбирает 100 строк за раз. Также возможен явный массовый сбор / массовая выборка.

Однако курсоры PL / SQL - это последнее средство, используйте их, когда не можете решить проблему с помощью SQL на основе наборов.

Другая причина - это распараллеливание. Базе данных проще распараллеливать большие операторы на основе наборов, чем построчный императивный код. По той же причине, почему функциональное программирование становится все более популярным (Haskell, F #, Lisp, C # LINQ, MapReduce ...), функциональное программирование упрощает распараллеливание. Число процессоров на компьютер растет, поэтому распараллеливание становится все более серьезной проблемой.

person Community    schedule 10.01.2009

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

person Community    schedule 13.11.2008
comment
у вас есть эталон или эталон для этого? я не заметил такой резкой деградации производительности ... но, может быть, в моих таблицах недостаточно строк, чтобы это имело значение (обычно миллион или меньше)? - person Steven A. Lowe; 13.11.2008
comment
о, подождите, я понимаю, что вы имеете в виду - но я бы никогда не стал защищать использование курсоров вместо операций над множеством, только не впадаю в крайности, чтобы избежать курсоров - person Steven A. Lowe; 13.11.2008
comment
Я помню, как в первый раз выполнял SQL. Нам пришлось импортировать 50 КБ ежедневных файлов данных из мэйнфрейма в базу данных SQL Server ... Я использовал курсор и обнаружил, что импорт с использованием курсора занимал около 26 часов .. Когда я перешел на операции на основе наборов, процесс занял 20 минут. - person Charles Bretana; 13.11.2008

В приведенных выше ответах недостаточно подчеркивается важность блокировки. Я не большой поклонник курсоров, потому что они часто приводят к блокировкам на уровне таблицы.

person Community    schedule 28.12.2008
comment
да спасибо! Без вариантов предотвращения этого (только чтение, только пересылка и т. Д.) Они, безусловно, будут, как и любая операция (сервер sql), которая занимает несколько строк, а затем несколько страниц строк. - person Steven A. Lowe; 29.12.2008
comment
?? Это проблема вашей стратегии блокировки, а НЕ курсоров. Даже оператор SELECT добавит блокировки чтения. - person Adam; 13.03.2019

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

person Community    schedule 14.11.2008
comment
Если под общим итогом вы имеете в виду агрегацию какого-либо вида (min, max, sum), любая компетентная СУБД превзойдет решение на стороне клиента, основанное на курсоре, хотя бы потому, что функция выполняется в движке и нет клиент ‹--› серверные накладные расходы. Может быть, SQL Server некомпетентен? - person Richard T; 28.12.2008
comment
@ [Ричард Т]: мы обсуждаем курсоры на стороне сервера, как внутри хранимой процедуры, а не курсоры на стороне клиента; извините за путаницу! - person Steven A. Lowe; 29.12.2008

Помимо проблем с производительностью (не), я думаю, что самый большой недостаток курсоров - их болезненная отладка. Особенно по сравнению с кодом в большинстве клиентских приложений, где отладка имеет тенденцию быть сравнительно простой, а языковые функции имеют тенденцию быть намного проще. Фактически, я утверждаю, что почти все, что делается в SQL с помощью курсора, вероятно, должно происходить в первую очередь в клиентском приложении.

person Community    schedule 09.06.2009
comment
SQL сложно отлаживать даже без курсоров. Пошаговые инструменты MS SQL в Visual Studio, похоже, мне не нравятся (они часто зависают или вообще не срабатывают по точкам останова), поэтому я обычно ограничиваюсь операторами PRINT ;-) - person Steven A. Lowe; 10.06.2009

Можете ли вы опубликовать этот пример курсора или ссылку на вопрос? Возможно, есть способ лучше, чем рекурсивный CTE.

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

person Community    schedule 13.11.2008
comment
есть способ получше - гребаный курсор ;-) - person Steven A. Lowe; 13.11.2008

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

Что касается вашего вопроса, хотя, безусловно, существуют ситуации, когда курсор может быть вызван, по моему опыту разработчики решают, что курсор «должен» использоваться ДАЛЬШЕ чаще, чем на самом деле. На мой взгляд, вероятность того, что кто-то ошибется в отношении слишком частого использования курсоров по сравнению с их неиспользованием, когда они должны были, НАМНОГО выше.

person Community    schedule 13.11.2008
comment
пожалуйста, прочти внимательнее, Том - точной фразой была безумная ненависть; прилагательное «безумный» было объектом ненависти, а не людей. Иногда английский может быть немного сложным ;-) - person Steven A. Lowe; 13.11.2008

в основном 2 блока кода, которые делают то же самое. может быть, это немного странный пример, но он подтверждает суть дела. SQL Server 2005:

SELECT * INTO #temp FROM master..spt_values
DECLARE @startTime DATETIME

BEGIN TRAN 

SELECT @startTime = GETDATE()
UPDATE #temp
SET number = 0
select DATEDIFF(ms, @startTime, GETDATE())

ROLLBACK 

BEGIN TRAN 
DECLARE @name VARCHAR

DECLARE tempCursor CURSOR
    FOR SELECT name FROM #temp

OPEN tempCursor

FETCH NEXT FROM tempCursor 
INTO @name

SELECT @startTime = GETDATE()
WHILE @@FETCH_STATUS = 0
BEGIN

    UPDATE #temp SET number = 0 WHERE NAME = @name
    FETCH NEXT FROM tempCursor 
    INTO @name

END 
select DATEDIFF(ms, @startTime, GETDATE())
CLOSE tempCursor
DEALLOCATE tempCursor

ROLLBACK 
DROP TABLE #temp

одно обновление занимает 156 мс, а курсора - 2016 мс.

person Community    schedule 13.11.2008
comment
ну да, это доказывает, что это действительно глупый способ использования курсора! но что, если обновление каждой строки зависит от значения предыдущей строки в порядке дат? - person Steven A. Lowe; 13.11.2008
comment
BEGIN TRAN SELECT TOP 1 baseval FROM table ORDER BY timestamp DESC INSERT table (fields) VALUES (vals, включая производное значение из предыдущей записи) COMMIT TRAN - person dkretz; 13.11.2008
comment
@doofledorfer: это будет вставлять одну строку на основе последней строки по дате, а не обновлять каждую строку по значению из ее предыдущей строки в порядке даты - person Steven A. Lowe; 14.11.2008
comment
Чтобы правильно использовать курсор, вы должны использовать WHERE CURRENT OF в обновлении - person erikkallen; 28.12.2008