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

127

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

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

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

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

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

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

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

Стивен А. Лоу
источник
1
Я думаю, что ваш Edit говорит обо всем ... Почти во всех ситуациях (с которыми я сталкивался) есть способ заменить курсор более эффективной ситуацией на основе набора. Вы говорите, что это понятно, но вы понимаете разницу.
StingyJack
7
Мне нравятся теги на этом вопросе!
sep332
2
То, что существуют ограничения рекурсивного CTE, 32- нонсенс. Предположительно вы думаете о рекурсивных триггеров и максимум @@NESTLEVELиз 32. Его можно задать в запросе OPTION (MAXRECURSION N)со значением по умолчанию 100и без 0ограничений.
Мартин Смит
@MartinSmith: теперь лимит по умолчанию составляет 100, а максимальный - 32 КБ sql-server-helper.com/error-messages/msg-310.aspx
Стивен А. Лоу,
Нет, это точно так же, как и во всех версиях SQL Server, поддерживающих рекурсивные CTE. Как сказано в вашей ссылке: «Когда указан 0, ограничение не применяется».
Мартин Смит

Ответы:

74

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

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

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

Медленный выпуск

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

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

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

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

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

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

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

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

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

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

От SQLTeam :

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

галв.
источник
6
этой статье 7 лет, как вы думаете, возможно, за это время что-то изменилось?
Стивен А. Лоу
1
Я также считаю, что курсоры действительно медленные, и их следует избегать. Однако, если OP имел в виду вопрос, который, как мне кажется, он был, тогда курсор был правильным решением (потоковая передача записей по одной из-за ограничений памяти).
rmeador
обновленная статья не исправляет измерения относительной скорости, но дает некоторые хорошие оптимизации и альтернативы. Обратите внимание, что в исходной статье говорится, что курсоры в 50 раз быстрее, чем циклы while, что интересно
Стивен А. Лоу
6
@BoltBait: Я лично думаю, что если вы делаете такие общие утверждения, вам не может быть 45 лет :-P
Стивен А. Лоу,
4
@BoltBait: Ребята, слезайте с моей лужайки!
Стивен А. Лоу
19

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

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

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

davidcl
источник
1
Похоже на исключение, подтверждающее правило.
Joel Coehoorn
6
@ [Джоэл Кохорн]: Я никогда не понимал этого высказывания.
Стивен А. Лоу
2
@ [Стивен А. Лоу] phrases.org.uk/meanings/exception-that-proves-the-rule.html понимает исключение как «то, что не учтено», и обратите внимание, что здесь правило выглядит примерно так: «в большинстве случаев курсоры плохой".
Дэвид Лэй
1
@delm: спасибо за ссылку, теперь фразу еще меньше понимаю!
Стивен А. Лоу
5
@ [Steven A. Lowe] По сути, это говорит о том, что если вы «нарушаете правило» с помощью подзадачи, должно быть общее правило, которое нужно нарушить, следовательно, правило существует. например , от Link: ( «Если у нас есть заявление , как„вход бесплатно по воскресеньям“, мы можем разумно предположить , что, как правило, запись за плату.»)
Фрай
9

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

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

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

davidcl
источник
9

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

Кейд Ру
источник
8

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

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

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

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

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

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

tuinstoel
источник
6

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

Чарльз Бретана
источник
у вас есть эталон или эталон для этого? я не заметил такой резкой деградации производительности ... но, возможно, в моих таблицах недостаточно строк, чтобы это имело значение (обычно миллион или меньше)?
Стивен А. Лоу
о, подождите, я понимаю, что вы имеете в виду - но я бы никогда не стал защищать использование курсоров вместо операций над множеством, только не впадаю в крайности, чтобы избежать курсоров,
Стивен А. Лоу
3
Я помню, как в первый раз выполнял SQL. Нам пришлось импортировать 50 КБ ежедневных файлов данных из мэйнфрейма в базу данных SQL Server ... Я использовал курсор и обнаружил, что импорт с использованием курсора занимает около 26 часов .. Когда я перешел на операции на основе наборов, процесс занял 20 минут.
Чарльз Бретана
6

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

Ричард Т
источник
1
да спасибо! Без вариантов предотвращения этого (только чтение, только пересылка и т. Д.) Они, безусловно, будут, как и любая операция (сервер sql), которая занимает несколько строк, а затем несколько страниц строк.
Стивен А. Лоу
?? Это проблема вашей стратегии блокировки, а НЕ курсоров. Даже оператор SELECT добавит блокировки чтения.
Adam
3

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

Эрик Сабин
источник
1
Если под «промежуточным итогом» вы имеете в виду агрегацию какого-либо вида (min, max, sum), любая компетентная СУБД превзойдет клиентское решение на основе курсора, хотя бы потому, что функция выполняется в движке и нет накладных расходов на клиент <--> на сервер. Может быть, SQL Server некомпетентен?
Ричард Т.
1
@ [Ричард Т]: мы обсуждаем курсоры на стороне сервера, как внутри хранимой процедуры, а не курсоры на стороне клиента; извините за путаницу!
Стивен А. Лоу
2

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

Уятт Барнетт
источник
2
SQL сложно отлаживать даже без курсоров. Пошаговые инструменты MS SQL в Visual Studio мне не нравятся (они часто зависают или вообще не срабатывают по точкам останова), поэтому я обычно ограничиваюсь операторами PRINT ;-)
Стивен А. Лоу
1

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

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

Гордон Белл
источник
1
есть способ получше - чертов курсор ;-)
Стивен А. Лоу
1

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

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

Том Х
источник
8
пожалуйста, прочти внимательнее, Том - точная фраза была «безумная ненависть»; «ненавидимый» был объектом прилагательного «безумный», а не «люди». Английский иногда может быть немного сложным ;-)
Стивен А. Лоу
0

в основном 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 мс.

Младен Прайдич
источник
3
ну да, это доказывает, что это действительно тупой способ использования курсора! но что, если обновление каждой строки зависит от значения предыдущей строки в порядке дат?
Стивен А. Лоу
BEGIN TRAN SELECT TOP 1 baseval FROM table ORDER BY timestamp DESC INSERT table (fields) VALUES (vals, включая производное значение из предыдущей записи) COMMIT TRAN
dkretz
@doofledorfer: это будет вставлять одну строку на основе последней строки по дате, а не обновлять каждую строку на значение из предыдущей строки в порядке дат
Стивен А. Лоу,
Чтобы действительно использовать курсор, вы должны использовать WHERE CURRENT OF в обновлении
erikkallen