Dlaczego ludzie tak bardzo nienawidzą kursorów SQL? [Zamknięte]

127

Rozumiem, że chcę uniknąć konieczności używania kursora ze względu na koszty ogólne i niedogodności, ale wygląda na to, że ma miejsce poważna mania-fobia-kursor, w której ludzie robią wiele, aby uniknąć konieczności używania jednego.

Na przykład jedno pytanie dotyczyło tego, jak zrobić coś oczywiście trywialnego z kursorem, a akceptowana odpowiedź zaproponowana za pomocą zapytania rekurencyjnego wspólnego wyrażenia tabelowego (CTE) z rekursywną funkcją niestandardową, mimo że ogranicza to liczbę wierszy, które można przetworzyć do 32 (ze względu na limit wywołań funkcji rekurencyjnych w serwerze sql). Wydaje mi się, że jest to straszne rozwiązanie zapewniające trwałość systemu, nie wspominając o ogromnym wysiłku, aby uniknąć używania prostego kursora.

Jaki jest powód tego poziomu szalonej nienawiści? Czy jakiś „uznany autorytet” wydał fatwę przeciwko kursorom? Czy w sercu kursorów czai się jakieś niewyobrażalne zło, które psuje moralność dzieci czy coś takiego?

Pytanie na Wiki, bardziej zainteresowane odpowiedzią niż przedstawicielem.

Powiązane informacje:

SQL Server Fast Forward Cursors

EDYCJA: chcę być bardziej precyzyjny: rozumiem, że kursory nie powinny być używane zamiast normalnych operacji relacyjnych ; to nie myślenia. To, czego nie rozumiem, to to, że ludzie robią wszystko, co w ich mocy, aby unikać kursorów, takich jak mają cipy lub coś w tym rodzaju, nawet jeśli kursor jest prostszym i / lub wydajniejszym rozwiązaniem. To irracjonalna nienawiść, która mnie zaskakuje, a nie oczywiste techniczne zalety.

Steven A. Lowe
źródło
1
Myślę, że Twoja Edycja mówi wszystko ... W prawie wszystkich sytuacjach (z którymi się spotkałem) istnieje sposób na zastąpienie kursora lepszą sytuacją opartą na zestawie. Mówisz bez myślenia, ale rozumiesz różnicę.
StingyJack
7
Uwielbiam tagi w tym pytaniu!
Wrz332
2
Część dotycząca rekurencyjnych granic CTE 32jest bezsensowna. Prawdopodobnie myślisz rekurencyjnych wyzwalaczy i max @@NESTLEVELod 32. Można ustawić w zapytaniu OPTION (MAXRECURSION N)z wartością domyślną 100i 0nieograniczoną.
Martin Smith
@MartinSmith: domyślny limit to teraz 100, a maksymalny to 32 tys. Sql-server-helper.com/error-messages/msg-310.aspx
Steven A. Lowe
1
@MartinSmith: dzięki, mój błąd - właściwie dwa błędy;) pierwszy to błędne odczytanie referencji (przyjąłem limit 32K = 'nieograniczony'), a drugi to zła przyczyna - w cytowanym przykładzie limit rekursji wynoszący 32 pochodzi z funkcja rekurencyjna, a nie CTE. Prawdopodobnie używałem wtedy SQL Server 2000, a może 2008, mam nadzieję, że teraz jest lepiej :). Pytanie zredagowane w celu wyjaśnienia - dziękujemy za korektę!
Steven A. Lowe

Odpowiedzi:

74

„Narzut” z kursorami to tylko część interfejsu API. Kursory wskazują, jak części RDBMS działają pod maską. Często CREATE TABLEi INSERTmają SELECTinstrukcje, a implementacja jest oczywistą implementacją wewnętrznego kursora.

Użycie „operatorów opartych na zbiorach” wyższego poziomu łączy wyniki kursora w jeden zestaw wyników, co oznacza mniej API w przód iw tył.

Kursory są starsze niż współczesne języki, które zapewniają zbiory pierwszej klasy. Stare C, COBOL, Fortran itd. Musiały przetwarzać wiersze pojedynczo, ponieważ nie istniało pojęcie „kolekcji”, które można by szeroko stosować. Java, C #, Python itp. Mają pierwszorzędne struktury list zawierające zestawy wyników.

Problem powolny

W niektórych kręgach łączenia relacyjne są tajemnicą i ludzie będą pisać zagnieżdżone kursory zamiast zwykłego łączenia. Widziałem naprawdę epickie zagnieżdżone operacje pętli zapisane jako wiele, wiele kursorów. Pokonanie optymalizacji RDBMS. I działa bardzo wolno.

Proste przepisywanie kodu SQL w celu zastąpienia zagnieżdżonych pętli kursora łączeniami, a pojedyncza, płaska pętla kursora może sprawić, że programy będą uruchamiane 100 razy. [Myśleli, że jestem bogiem optymalizacji. Wszystko, co zrobiłem, to zastąpienie zagnieżdżonych pętli łączeniami. Nadal używano kursorów.]

To zamieszanie często prowadzi do postawienia kuratorów w stan oskarżenia. Jednak to nie kursor, tylko niewłaściwe użycie kursora jest problemem.

Kwestia rozmiaru

W przypadku naprawdę imponujących zestawów wyników (tj. Zrzucania tabeli do pliku) niezbędne są kursory. Operacje oparte na zbiorach nie mogą zmaterializować naprawdę dużych zestawów wyników jako pojedynczej kolekcji w pamięci.

Alternatywy

W miarę możliwości staram się używać warstwy ORM. Ale ma to dwa cele. Po pierwsze, kursorami zarządza komponent ORM. Po drugie, SQL jest oddzielony od aplikacji do pliku konfiguracyjnego. Nie chodzi o to, że kursory są złe. Chodzi o to, że kodowanie tych wszystkich operacji otwierania, zamykania i pobierania nie jest programowaniem z wartością dodaną.

S.Lott
źródło
3
„Kursory to sposób działania RDBMS pod maską”. Jeśli masz na myśli konkretnie SQL Server, OK, dobrze, nic o tym nie wiem. Ale pracowałem nad wewnętrznymi elementami wielu RDBMS (i ORDBMS) (pod firmą Stonebraker) i żaden z nich tego nie zrobił. Np .: Ingres wewnętrznie używa tego, co sprowadza się do „zbiorów wyników” krotek.
Richard T
@Richard T: Pracuję nad informacjami z drugiej ręki o źródle RDBMS; Poprawię oświadczenie.
S.Lott
2
„Widziałem naprawdę epickie, zagnieżdżone operacje w pętli, zapisane jako wiele, wiele kursorów”. Też je widuję. Aż trudno w to uwierzyć.
RussellH
42

Kursory powodują, że ludzie nadmiernie stosują proceduralny sposób myślenia w środowisku opartym na zestawie.

I są WOLNE !!!

Z SQLTeam :

Należy pamiętać, że kursory to NAJWOLNIEJSZA metoda uzyskiwania dostępu do danych w SQL Server. Powinien być używany tylko wtedy, gdy naprawdę potrzebujesz dostępu do jednego wiersza na raz. Jedynym powodem, dla którego przychodzi mi do głowy, jest wywołanie procedury składowanej w każdym wierszu. W artykule Cursor Performance odkryłem, że kursory są ponad trzydzieści razy wolniejsze niż alternatywy oparte na zestawach .

Galwegian
źródło
6
ten artykuł ma 7 lat, myślisz, że być może w międzyczasie coś się zmieniło?
Steven A. Lowe
1
Uważam też, że kursory są bardzo powolne i ogólnie należy ich unikać. Jeśli jednak OP odnosił się do pytania, które moim zdaniem był, to kursor był tam właściwym rozwiązaniem (przesyłanie strumieniowe rekordów pojedynczo ze względu na ograniczenia pamięci).
rmeador
zaktualizowany artykuł nie koryguje względnych pomiarów prędkości, ale zapewnia dobre optymalizacje i alternatywy. Zwróć uwagę, że oryginalny artykuł mówi, że kursory są 50 razy szybsze niż pętle while, co jest interesujące
Steven A. Lowe
6
@BoltBait: Osobiście uważam, że jeśli wygłaszasz takie ogólne stwierdzenia, nie możesz mieć 45 lat :-P
Steven A. Lowe
4
@BoltBait: Dzieciaki zejdźcie z mojego trawnika!
Steven A. Lowe,
19

Powyżej znajduje się odpowiedź, która mówi: „kursory są NAJLEPSZYM sposobem uzyskiwania dostępu do danych w SQL Server ... kursory są ponad trzydzieści razy wolniejsze niż alternatywy oparte na zestawach”.

To stwierdzenie może być prawdziwe w wielu okolicznościach, ale jako ogólne stwierdzenie jest problematyczne. Na przykład dobrze wykorzystałem kursory w sytuacjach, w których chcę wykonać operację aktualizacji lub usunięcia wpływającą na wiele wierszy dużej tabeli, która otrzymuje ciągłe odczyty produkcyjne. Uruchomienie procedury składowanej, która wykonuje te aktualizacje po jednym wierszu na raz, kończy się szybciej niż operacje oparte na zestawie, ponieważ operacja oparta na zestawie powoduje konflikt z operacją odczytu i powoduje przerażające problemy z blokowaniem (i może całkowicie zabić system produkcyjny, w ekstremalnych przypadkach).

W przypadku braku innych działań na bazie danych operacje oparte na zbiorach są zwykle szybsze. W systemach produkcyjnych to zależy.

davidcl
źródło
1
Brzmi jak wyjątek potwierdzający regułę.
Joel Coehoorn
6
@ [Joel Coehoorn]: Nigdy nie rozumiałem tego powiedzenia.
Steven A. Lowe
2
@ [Steven A. Lowe] phrases.org.uk/meanings/exception-that-proves-the-rule.html rozumieją wyjątek jako „to, co jest pomijane ” i zauważ, że reguła jest podobna do „w większości sytuacji kursory są zły".
David Lay
1
@delm: dzięki za link, teraz jeszcze mniej rozumiem to zdanie!
Steven A. Lowe
5
@ [Steven A. Lowe] Zasadniczo chodzi o to, że jeśli „złamiesz regułę” za pomocą podpunktu, musi istnieć ogólna reguła do złamania, a zatem reguła istnieje. np. From Link: („Jeśli mamy stwierdzenie typu„ wstęp jest bezpłatny w niedziele ”, możemy rozsądnie założyć, że z reguły wstęp jest płatny.”)
Fry
9

Kursory są zwykle używane przez początkujących programistów SQL w miejscach, w których operacje oparte na zbiorach byłyby lepsze. Szczególnie, gdy ludzie uczą się SQL po nauczeniu się tradycyjnego języka programowania, mentalność „iteracji po tych rekordach” prowadzi ludzi do niewłaściwego używania kursorów.

Najpoważniejsze książki o języku SQL zawierają rozdział zalecający używanie kursorów; dobrze napisane jasno pokazują, że kursory mają swoje miejsce, ale nie powinny być używane do operacji opartych na zbiorach.

Są oczywiście sytuacje, w których kursory są właściwym wyborem, a przynajmniej właściwym wyborem.

davidcl
źródło
9

Optymalizator często nie może użyć algebry relacyjnej do przekształcenia problemu, gdy używana jest metoda kursora. Często kursor jest świetnym sposobem rozwiązania problemu, ale SQL jest językiem deklaratywnym, aw bazie danych jest wiele informacji, od ograniczeń po statystyki i indeksy, co oznacza, że ​​optymalizator ma wiele opcji rozwiązania problemu problem, podczas gdy kursor dość wyraźnie kieruje rozwiązaniem.

Cade Roux
źródło
8

W Oracle PL / SQL kursory nie spowodują blokowania tabel i możliwe jest użycie zbiorczego gromadzenia / pobierania zbiorczego.

W Oracle 10 często używany niejawny kursor

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

pobiera niejawnie 100 wierszy naraz. Możliwe jest również jawne gromadzenie / pobieranie zbiorcze.

Jednak kursory PL / SQL są czymś w rodzaju ostateczności, używaj ich, gdy nie możesz rozwiązać problemu z SQL opartym na zestawie.

Innym powodem jest zrównoleglenie, ponieważ baza danych jest łatwiejsza do zrównoleglenia dużych instrukcji opartych na zestawie niż kod imperatywny wiersz po wierszu. Z tego samego powodu programowanie funkcjonalne staje się coraz bardziej popularne (Haskell, F #, Lisp, C # LINQ, MapReduce ...), programowanie funkcjonalne ułatwia równoległość. Liczba procesorów na komputer rośnie, więc zrównoleglenie staje się coraz większym problemem.

tuinstoel
źródło
6

Ogólnie rzecz biorąc, ponieważ w relacyjnej bazie danych wydajność kodu używającego kursorów jest o rząd wielkości gorsza niż operacje na zbiorach.

Charles Bretana
źródło
czy masz do tego punkt odniesienia lub odniesienie? Nie zauważyłem żadnego tak drastycznego spadku wydajności ... ale może moje tabele nie mają wystarczającej liczby wierszy, aby miało to znaczenie (zwykle milion lub mniej)?
Steven A. Lowe
och, czekaj, rozumiem, co masz na myśli - ale nigdy nie zalecałbym używania kursorów w ramach operacji na zbiorach, tylko nie posuwał się do skrajności w celu uniknięcia kursorów
Steven A. Lowe
3
Pamiętam, jak pierwszy raz wykonywałem SQL, musieliśmy importować plik danych 50k dziennie z komputera mainframe do bazy danych SQL Server ... Użyłem kursora i odkryłem, że import trwa około 26 godzin przy użyciu kursora. Kiedy przeszedłem na operacje oparte na zbiorach, proces ten trwał 20 minut.
Charles Bretana
6

Powyższe odpowiedzi nie podkreśliły wystarczająco znaczenia blokowania. Nie jestem wielkim fanem kursorów, ponieważ często powodują blokady na poziomie tabeli.

Richard T.
źródło
1
tak dziękuję! Bez opcji zapobiegających temu (tylko do odczytu, tylko do przodu itp.) Z pewnością tak się stanie, podobnie jak każda operacja (serwer sql), która zajmuje kilka wierszy, a następnie kilka stron wierszy.
Steven A. Lowe
?? To problem z twoją strategią blokowania, a nie kursorami. Nawet instrukcja SELECT doda blokady odczytu.
Adam
3

Co jest warte, przeczytałem, że "jedno" miejsce, na którym kursor wykona, jego odpowiednik oparty na zestawie jest w sumie bieżącej. W przypadku małej tabeli szybkość sumowania wierszy w kolejności według kolumn faworyzuje operację opartą na zestawie, ale gdy tabela zwiększa rozmiar wiersza, kursor będzie stawał się szybszy, ponieważ może po prostu przenosić bieżącą wartość całkowitą do następnego przebiegu pętla. Teraz, gdzie powinieneś zrobić sumę bieżącą, to inny argument ...

Eric Sabine
źródło
1
Jeśli rozumiesz przez „całkowitą sumę” jakiejś agregacji (min, maksimum, suma), każdy kompetentny DBMS pokonuje spodnie rozwiązania opartego na kursorach po stronie klienta, choćby dlatego, że funkcja jest wykonywana w silniku i nie ma narzutu klienta <--> serwera. Może SQL Server nie jest kompetentny?
Richard T
1
@ [Richard T]: omawiamy kursory po stronie serwera, jak w ramach procedury składowanej, a nie kursory po stronie klienta; przepraszam za zamieszanie!
Steven A. Lowe
2

Poza (nie) problemami z wydajnością, myślę, że największą wadą kursorów jest to, że ich debugowanie jest bolesne. Zwłaszcza w porównaniu z kodem w większości aplikacji klienckich, w których debugowanie jest stosunkowo łatwe, a funkcje językowe są znacznie łatwiejsze. W rzeczywistości uważam, że prawie wszystko, co robi się w SQL za pomocą kursora, powinno prawdopodobnie mieć miejsce w aplikacji klienckiej.

Wyatt Barnett
źródło
2
SQL jest trudny do debugowania, nawet bez kursorów. Narzędzia MS SQL Step-through w Visual Studio nie wydają się mnie lubić (często się zawieszają lub w ogóle nie wyzwalają punktów przerwania), więc zwykle ograniczam się do poleceń PRINT ;-)
Steven A. Lowe
1

Czy możesz opublikować przykład tego kursora lub link do pytania? Prawdopodobnie jest jeszcze lepszy sposób niż rekurencyjne CTE.

Oprócz innych komentarzy, niewłaściwe użycie kursorów (co często się zdarza) powoduje niepotrzebne blokowanie stron / wierszy.

Gordon Bell
źródło
1
jest lepszy sposób - cholerny kursor ;-)
Steven A. Lowe
1

Prawdopodobnie mógłbyś zakończyć swoje pytanie po drugim akapicie, zamiast nazywać ludzi „szalonymi” po prostu dlatego, że mają inny punkt widzenia niż ty i w inny sposób próbując kpić z profesjonalistów, którzy mogą mieć bardzo dobry powód, by czuć się tak, jak oni.

Jeśli chodzi o twoje pytanie, chociaż z pewnością są sytuacje, w których można wywołać kursor, z mojego doświadczenia wynika, że ​​programiści decydują, że kursor „musi” być używany znacznie częściej niż jest to w rzeczywistości. Szansa, że ​​ktoś pomyli się po stronie zbyt częstego używania kursorów vs. nieużywanie ich, kiedy powinny, jest moim zdaniem DUŻO wyższa.

Tom H.
źródło
8
przeczytaj uważniej, Tom - dokładne wyrażenie brzmiało „szalona nienawiść”; „znienawidzony” był przedmiotem przymiotnika „szalony”, a nie „ludzie”. Angielski bywa czasem trochę trudny ;-)
Steven A. Lowe
0

w zasadzie 2 bloki kodu, które robią to samo. może to trochę dziwny przykład, ale to potwierdza. 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

pojedyncza aktualizacja trwa 156 ms, podczas gdy kursor trwa 2016 ms.

Mladen Prajdic
źródło
3
no tak, to udowadnia, że ​​jest to naprawdę głupi sposób używania kursora! ale co się stanie, jeśli aktualizacja każdego wiersza zależy od wartości poprzedniego wiersza w kolejności dat?
Steven A. Lowe
BEGIN TRAN SELECT TOP 1 baseval FROM table ORDER BY timestamp DESC INSERT tabela (pola) VALUES (vals, w tym wartość pochodna z poprzedniego rekordu) COMMIT TRAN
dkretz
@doofledorfer: to wstawiłoby jeden wiersz na podstawie ostatniego wiersza według daty, a nie zaktualizowałoby każdy wiersz o wartość z poprzedniego wiersza w kolejności dat
Steven A. Lowe
Aby naprawdę używać kursora, powinieneś użyć WHERE CURRENT OF w aktualizacji
erikkallen