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


127

Я можу зрозуміти, що хочу уникнути необхідності використання курсору через накладні витрати і незручності, але схоже, що існує якась серйозна манія курсору-фобії, де люди збираються з великими зусиллями, щоб уникнути їх використання.

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

У чому причина такого рівня шаленої ненависті? Хтось із «помічених авторитетів» видав фетву проти курсорів? Чи якесь невимовне зло ховається в серці курсорів, що розбещує мораль дітей чи щось таке?

Питання Wiki, більше зацікавлене у відповіді, ніж відповідь.

Інформація про те:

Курсори швидкого переходу вперед SQL Server

EDIT: дозвольте бути більш точним: я розумію, що курсори не слід використовувати замість звичайних реляційних операцій ; це нереалізатор. Що я не розумію, це те, що люди відмовляються від шляху, щоб уникнути курсорів, як-от у них є печиво чи щось подібне, навіть коли курсор є більш простим та / або більш ефективним рішенням. Мене бентежить нераціональна ненависть, а не очевидна технічна ефективність.


1
Я думаю, що ваша редакція говорить про це все ... Майже у всіх ситуаціях (на які я натрапила) є спосіб замінити курсор на більш ефективну набір ситуацій. Ви кажете, що не мислите, але ви розумієте різницю.
StingyJack

7
Я люблю теги з цього питання!
sep332

2
Частина про рекурсивні обмеження CTE - 32це нісенітниця. Імовірно ви думаєте про рекурсивних тригерів і максимум @@NESTLEVELз 32. Він може бути встановлений у запиті, OPTION (MAXRECURSION N)за замовчуванням 100і 0значенням необмежений.
Мартін Сміт

@MartinSmith: ліміт за замовчуванням зараз 100, а макс. 32K sql-server-helper.com/error-messages/msg-310.aspx
Стівен А. Лоу

Ні, це все одно точно так само, як коли я зробив свій коментар і в усіх версіях SQL Server, які підтримують рекурсивні CTE. Як зазначається у вашому посиланні, "Коли вказано 0, обмеження не застосовується".
Мартін Сміт

Відповіді:


74

"Накладні витрати" з курсорами - лише частина API. Курсори - це те, як частини RDBMS працюють під кришкою. Часто CREATE TABLEі INSERTєSELECT заяви, і реалізація є очевидним внутрішнім курсором реалізації.

Використовуючи "оператори на основі набору", зв'язує результати курсору в єдиний набір результатів, що означає менше API назад і вперед.

Курсори передують сучасним мовам, які пропонують першокласні колекції. Старі C, COBOL, Fortran тощо повинні були обробляти рядки по одному, оскільки не було поняття "колекція", яке можна було б широко використовувати. У Java, C #, Python та ін. Є структури першокласного списку, які містять набори результатів.

Повільний випуск

У деяких колах реляційні приєднання є загадкою, і люди будуть писати вкладені курсори, а не просте з'єднання. Я бачив по-справжньому епічні операції вкладеного циклу, виписані у вигляді безлічі курсорів. Поразка оптимізації RDBMS. І біжить справді повільно.

Прості SQL переписуються, щоб замінити вкладені петлі курсору приєднаннями, а одна, плоска петля курсору, може змусити програми запускатись у 100-й раз. [Вони думали, що я бог оптимізації. Все, що я зробив, було замінити вкладені петлі з'єднаннями. Ще використовуються курсори.]

Ця плутанина часто призводить до звинувачення у кримінальних справах. Однак це не курсор, це неправильне використання курсору.

Випуск розміру

Для дійсно епічних наборів результатів (тобто, перекидання таблиці у файл) курсори є важливими. Операції на основі набору не можуть реалізувати дійсно великі набори результатів як єдину колекцію в пам'яті.

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

Я намагаюся максимально використовувати шар ORM. Але це має дві цілі. По-перше, курсорами керує компонент ORM. По-друге, SQL відокремлюється від програми в файл конфігурації. Справа не в тому, що курсори погані. Справа в тому, що кодування всіх відкритих, закритих і витягуваних програм не є додатковою програмою.


3
"Курсори - це те, як RDBMS працює під кришкою." Якщо ви конкретно маєте на увазі SQL Server, гаразд, добре, я не знаю цього. Але я працював над внутрішніми системами декількох RDBMS (і ORDBMS) (під Stonebraker), і жоден з них цього не робив. Напр .: Інгрес використовує те, що означає "набір результатів" кортежів внутрішньо.
Річард Т

@Richard T: я опрацьовую інформацію, що була б у використанні, про джерело RDBMS; Я зміню заяву.
С.Лотт

2
"Я бачив по-справжньому епічні операції вкладених циклів, записані як багато і багато курсорів". Я постійно їх бачу. У це важко повірити.
RussellH

41

Курсори змушують людей надмірно застосовувати процедурний спосіб мислення до встановленого середовища.

І вони РОЗУМНІ !!!

Від SQLTeam :

Зауважте, що курсори - це НАЙСЛОВНІший спосіб доступу до даних всередині SQL Server. Це слід використовувати лише тоді, коли вам справді потрібно мати доступ до одного рядка за раз. Єдина причина, про яку я можу подумати, - це викликати збережену процедуру в кожному рядку. У статті про ефективність курсору я виявив, що курсори втридцять разів повільніше, ніж встановлені альтернативи .


6
цій статті 7 років, ти вважаєш, що, можливо, тим часом щось змінилося?
Стівен А. Лоу

1
Я також думаю, що курсори дійсно повільні і їх взагалі слід уникати. Однак якщо ОП посилалося на питання, на який я думаю, що він є, то правильним рішенням там був курсор (потокове записування по черзі через обмеження пам'яті).
rmeador

оновлена ​​стаття не виправляє відносні вимірювання швидкості, але вона дає хороші оптимізації та альтернативи. Зауважимо, що в оригінальній статті йдеться про те, що курсори в 50 разів швидші, ніж петлі, що цікаво
Стівен А. Лоу

6
@BoltBait: Я особисто вважаю, що якщо ти будеш робити такі твердження, що тобі не виповниться 45 років:
Стівен А. Лоу

4
@BoltBait: Діти, виходьте з моєї галявини!
Стівен А. Лоу

19

Вище є відповідь, в якій сказано, що "курсори - НАЙСЛОВНІший спосіб доступу до даних всередині SQL Server ... курсори більш ніж у тридцять разів повільніші, ніж встановлені альтернативи".

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

За відсутності іншої діяльності з базами даних, операції на основі заданих програм є універсально швидшими. У виробничих системах це залежить.


1
Здається, виняток, який підтверджує правило.
Joel Coehoorn

6
@ [Джоел Куехорн]: Я ніколи не розумів цього вислову.
Стівен А. Лоу

2
@ [Стівен А. Лоу] phrases.org.uk/meanings/exception-that-proves-the-rule.html розуміє виняток як "те, що залишилось поза увагою ", і зауважте, що правило тут є чимось на зразок "у більшості ситуаційних курсорів є погано ».
Девід Лежав

1
@delm: дякую за посилання, тепер я розумію фразу ще менше!
Стівен А. Лоу

5
@ [Стівен А. Лоу] В основному це говорить про те, що якщо ви "порушите правило" з підзаголовком, повинно бути загальне правило, щоб його порушити, ерго правило існує. наприклад, із посилання: ("Якщо у нас є заява на кшталт" вхід безкоштовний у неділю ", ми можемо обгрунтовано припустити, що за загальним правилом запис стягується.")
Світ

9

Курсори, як правило, використовуються для запуску розробників SQL в тих місцях, де операції на основі набору були б кращими. Зокрема, коли люди вивчають SQL після вивчення традиційної мови програмування, менталітет "повторення цих записів" має тенденцію змусити людей використовувати неправильно використовувати курсори.

Більшість серйозних книг SQL включає в себе розділ, що користується курсорами; добре написані чітко дають зрозуміти, що курсори мають своє місце, але їх не слід використовувати для встановлених операцій.

Очевидно, є ситуації, коли курсори - правильний вибір, або принаймні правильний вибір.


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 ...), функціональне програмування полегшує паралелізацію. Кількість процесорів на комп'ютері зростає, тому паралелізація стає все більшою проблемою.


6

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


у вас є орієнтир або посилання на це? я не помічав жодної такої різкої погіршення продуктивності ... але, можливо, мої таблиці не мають достатньо рядків, щоб це мало значення (зазвичай мільйон або менше)?
Стівен А. Лоу

о зачекайте, я бачу, що ви маєте на увазі - але я ніколи б не рекомендував використовувати курсори для встановлених операцій, тільки не збираюся в крайностях, щоб уникнути курсорів
Стівен А. Лоу

3
Я пам’ятаю, коли я вперше робив SQL, нам довелося імпортувати 50-денний файл даних з мейнфрейму в базу даних SQL Server… Я використав курсор і виявив, що імпорт займає близько 26 годин за допомогою курсору .. Коли я перейшов на встановлені операції, процес зайняв 20 хвилин.
Чарльз Бретана

6

Наведені відповіді недостатньо підкреслили важливість блокування. Я не великий фанат курсорів, оскільки вони часто призводять до блокування рівнів таблиці.


1
так дякую! Без варіантів його запобігання (лише читання, лише вперед тощо) вони, безумовно, зроблять так само, як і будь-яка операція (сервер sql), яка займе кілька рядків, а потім декілька сторінок рядків.
Стівен А. Лоу

?? У цьому проблема вашої стратегії блокування НЕ курсорів. Навіть оператор SELECT додасть блокування читання.
Адам

3

Для чого варто прочитати, що курсор "єдиного" місця виконуватиме свій аналог на основі заданих значень. У невеликій таблиці швидкість підсумовування рядків над порядком за стовпцями сприяє операції на основі набору, але в міру збільшення таблиці в розмірі рядків курсор стане швидшим, оскільки він може просто перенести поточне загальне значення до наступного проходу петля. Тепер, де вам слід виконати загальну суму - це інший аргумент ...


1
Якщо ви маєте на увазі під "загальним виконанням" деяку агрегацію (хв, макс, сума), будь-яка компетентна СУБД відбиває штани від клієнтського рішення, заснованого на курсорі, хоча б тому, що функція виконується в двигуні та немає клієнта <--> накладних серверів. Можливо, SQL Server не компетентний?
Річард Т

1
@ [Річард Т]: ми обговорюємо курсори на стороні сервера, як в рамках збереженої процедури, а не курсори на стороні клієнта; Вибачте за непорозуміння!
Стівен А. Лоу


2

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


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

1

Чи можете ви опублікувати цей приклад курсору чи посилання на запитання? Можливо, є навіть кращий спосіб, ніж рекурсивний CTE.

Крім інших коментарів, курсори при неправильному використанні (що часто) викликають непотрібні блокування сторінок / рядків.


1
є кращий спосіб - химерний курсор ;-)
Стівен А. Лоу

1

Можливо, ви могли б закінчити своє запитання після другого абзацу, а не називати людей "божевільними", просто тому, що вони мають іншу точку зору, ніж ви, і в іншому випадку намагаєтесь знущатися над професіоналами, які можуть мати дуже вагомі причини відчувати себе так, як вони роблять.

Що стосується вашого питання, хоча, звичайно, є ситуації, коли курсор може бути викликаний, але, на мій досвід, розробники вирішують, що курсор "повинен" використовуватися FAR частіше, ніж є насправді. Шанс того, що хтось помилиться на стороні занадто великого використання курсорів проти їх використання, коли вони повинні, НАБАГО вище на мою думку.


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
ну так, це доводить те, що це дійсно німий спосіб використання курсору! але що робити, якщо оновлення кожного рядка залежало від значення попереднього рядка в порядку впорядкування?
Стівен А. Лоу

НАЧАЛО TRAN SELECT TOP 1 baseval З таблиці ЗАМОВИТИ ЗА часовою маркою DESC ВСТАВИТЬ таблицю (поля) ЦІННОСТІ (вали, включаючи отримане значення з попереднього запису) COMMIT TRAN
dkretz

@doofledorfer: щоб вставити один рядок на основі останнього рядка за датою, а не оновлювати кожний рядок значенням з його попереднього рядка в порядку впорядкування
Стівен А. Лоу

Щоб справді використовувати курсор, ви повинні використовувати WHERE CURRENT OF в оновлення
ТОЧАЛЬНИЙ ОН erikkallen
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.