Mengapa orang sangat membenci kursor SQL? [Tutup]

127

Saya dapat memahami keinginan untuk menghindari keharusan menggunakan kursor karena overhead dan ketidaknyamanan, tetapi sepertinya ada beberapa kursor-fobia-mania serius yang terjadi di mana orang akan berusaha keras untuk menghindari keharusan menggunakannya.

Misalnya, satu pertanyaan bertanya bagaimana melakukan sesuatu yang jelas sepele dengan kursor dan jawaban yang diterima diajukan menggunakan kueri rekursif ekspresi tabel umum (CTE) dengan fungsi kustom rekursif, meskipun ini membatasi jumlah baris yang dapat diproses menjadi 32 (Karena batas panggilan fungsi rekursif di sql server). Ini menurut saya sebagai solusi yang mengerikan untuk umur panjang sistem, belum lagi upaya luar biasa hanya untuk menghindari menggunakan kursor sederhana.

Apa alasan untuk tingkat kebencian gila ini? Sudahkah beberapa 'otoritas terkenal' mengeluarkan fatwa terhadap kursor? Apakah beberapa kejahatan yang tak terkatakan mengintai di jantung kursor yang merusak moral anak-anak atau sesuatu?

Pertanyaan wiki, lebih tertarik pada jawaban daripada rep.

Info Terkait:

Kursor Maju Cepat SQL Server

EDIT: izinkan saya lebih tepat: Saya mengerti bahwa kursor tidak boleh digunakan sebagai pengganti operasi relasional normal ; itu adalah no-brainer. Yang tidak saya mengerti adalah orang-orang akan keluar dari jalan mereka untuk menghindari kursor seperti mereka memiliki barang rampasan atau sesuatu, bahkan ketika kursor adalah solusi yang lebih sederhana dan / atau lebih efisien. Kebencian irasional yang membuatku bingung, bukan efisiensi teknis yang jelas.

Steven A. Lowe
sumber
1
Saya pikir Edit Anda mengatakan itu semua ... Di hampir semua situasi (yang saya temui) ada cara untuk mengganti kursor dengan situasi berbasis set berkinerja lebih baik. Anda mengatakan tidak punya otak, tetapi Anda memahami perbedaannya.
StingyJack
7
Saya suka tag pada pertanyaan ini!
sep332
2
Bagian tentang batas CTE rekursif 32adalah omong kosong. Mungkin Anda berpikir untuk pemicu rekursif dan max @@NESTLEVELdari 32. Itu dapat diatur dalam kueri OPTION (MAXRECURSION N)dengan default 100dan 0artinya tidak terbatas.
Martin Smith
@ MartinSmith: batas default sekarang adalah 100, dan maks adalah 32K sql-server-helper.com/error-messages/msg-310.aspx
Steven A. Lowe
Tidak, itu masih persis sama seperti ketika saya berkomentar dan di semua versi SQL Server yang mendukung CTE rekursif. Seperti tautan Anda mengatakan "Ketika 0 ditentukan, tidak ada batasan yang diterapkan."
Martin Smith

Jawaban:

74

"Overhead" dengan kursor hanyalah bagian dari API. Kursor adalah cara kerja bagian RDBMS di bawah tenda. Seringkali CREATE TABLEdan INSERTmemiliki SELECTpernyataan, dan implementasinya adalah implementasi kursor internal yang jelas.

Menggunakan "operator berbasis setel" yang lebih tinggi, menggabungkan hasil kursor ke dalam satu set hasil tunggal, yang berarti lebih sedikit API bolak-balik.

Kursor mendahului bahasa modern yang menyediakan koleksi kelas satu. Old C, COBOL, Fortran, dll., Harus memproses baris satu per satu karena tidak ada gagasan "koleksi" yang dapat digunakan secara luas. Java, C #, Python, dll., Memiliki struktur daftar kelas satu yang berisi set hasil.

Masalah Lambat

Di beberapa lingkaran, relasional bergabung adalah sebuah misteri, dan orang-orang akan menulis kursor bersarang daripada gabung sederhana. Saya telah melihat operasi berulang bersarang epik ditulis sebagai banyak dan banyak kursor. Mengalahkan optimasi RDBMS. Dan berjalan sangat lambat.

SQL rewrites sederhana untuk menggantikan loop kursor bersarang dengan gabungan dan satu, kursor datar dapat membuat program berjalan dalam waktu ke-100. [Mereka pikir saya adalah dewa optimasi. Yang saya lakukan adalah mengganti loop bersarang dengan bergabung. Masih menggunakan kursor.]

Kebingungan ini sering mengarah pada dakwaan kursor. Namun, itu bukan kursor, itu adalah penyalahgunaan kursor itulah masalahnya.

Masalah Ukuran

Untuk set hasil yang benar-benar epik (yaitu, membuang tabel ke file), kursor sangat penting. Operasi berbasis set tidak dapat mematerialisasi set hasil yang sangat besar sebagai satu kumpulan dalam memori.

Alternatif

Saya mencoba menggunakan lapisan ORM sebanyak mungkin. Tetapi itu memiliki dua tujuan. Pertama, kursor dikelola oleh komponen ORM. Kedua, SQL dipisahkan dari aplikasi menjadi file konfigurasi. Bukan karena kursornya buruk. Ini adalah pengkodean semua yang terbuka, ditutup dan diambil bukan pemrograman nilai tambah.

S.Lott
sumber
3
"Kursor adalah cara RDBMS bekerja di bawah tenda." Jika Anda maksud khusus SQL Server, OK, baik, saya tidak tahu itu. Tetapi saya telah mengerjakan internal beberapa RDBMS (dan ORDBMS) (di bawah Stonebraker) dan tidak satupun dari mereka yang melakukannya. Misalnya: Ingres menggunakan jumlah apa untuk "set hasil" dari tupel secara internal.
Richard T
@ Richard T: Saya mengerjakan informasi dari tangan kedua tentang sumber RDBMS; Saya akan mengubah pernyataan itu.
S.Lott
2
"Aku telah melihat operasi loop bersarang epik ditulis sebagai banyak dan banyak kursor." Saya terus melihat mereka juga. Sulit dipercaya.
RussellH
41

Kursor membuat orang terlalu menerapkan pola pikir prosedural ke lingkungan berbasis set.

Dan mereka lambat !!!

Dari SQLTeam :

Harap dicatat bahwa kursor adalah cara TERLambat untuk mengakses data di dalam SQL Server. Seharusnya hanya digunakan ketika Anda benar-benar perlu mengakses satu baris pada suatu waktu. Satu-satunya alasan yang dapat saya pikirkan adalah untuk memanggil prosedur tersimpan pada setiap baris. Dalam artikel Kinerja Kursor saya menemukan bahwa kursor lebih dari tiga puluh kali lebih lambat daripada menetapkan alternatif berbasis .

Belanda
sumber
6
artikel itu berumur 7 tahun, apakah menurut Anda mungkin banyak hal telah berubah sementara itu?
Steven A. Lowe
1
Saya juga berpikir kursor sangat lambat dan harus dihindari, secara umum. Namun, jika OP mengacu pada pertanyaan saya pikir dia, maka kursor adalah solusi yang tepat di sana (streaming merekam satu per satu karena keterbatasan memori).
rmeador
artikel yang diperbarui tidak mengoreksi pengukuran kecepatan relatif, tetapi memberikan beberapa optimasi dan alternatif yang baik. Perhatikan bahwa artikel asli mengatakan bahwa kursor 50 kali lebih cepat daripada saat loop, yang menarik
Steven A. Lowe
6
@BoltBait: Saya pribadi berpikir bahwa jika Anda membuat pernyataan selimut seperti itu, Anda tidak dapat benar-benar berusia 45 tahun :-P
Steven A. Lowe
4
@BoltBait: Kalian turun dari halaman saya!
Steven A. Lowe
19

Ada jawaban di atas yang mengatakan "kursor adalah cara TERLambat untuk mengakses data di dalam SQL Server ... kursor lebih dari tiga puluh kali lebih lambat daripada menetapkan alternatif berbasis."

Pernyataan ini mungkin benar dalam banyak keadaan, tetapi sebagai pernyataan selimut itu bermasalah. Sebagai contoh, saya telah memanfaatkan kursor dalam situasi di mana saya ingin melakukan operasi pembaruan atau penghapusan yang memengaruhi banyak baris tabel besar yang menerima pembacaan produksi konstan. Menjalankan prosedur tersimpan yang melakukan pembaruan ini satu demi satu berakhir lebih cepat daripada operasi berbasis set, karena operasi berbasis set bertentangan dengan operasi baca dan akhirnya menyebabkan masalah penguncian yang mengerikan (dan dapat mematikan sistem produksi sepenuhnya, dalam kasus ekstrim).

Dengan tidak adanya aktivitas database lain, operasi berbasis set secara universal lebih cepat. Dalam sistem produksi, itu tergantung.

davidcl
sumber
1
Kedengarannya seperti pengecualian yang membuktikan aturan.
Joel Coehoorn
6
@ [Joel Coehoorn]: Saya tidak pernah mengerti ucapan itu.
Steven A. Lowe
2
@ [Steven A. Lowe] phrases.org.uk/meanings/exception-that-proves-the-rule.html memahami pengecualian sebagai "apa yang tertinggal" dan perhatikan bahwa aturan di sini adalah sesuatu seperti "dalam kebanyakan situasi kursor adalah buruk".
David Lay
1
@delm: terima kasih untuk tautannya, sekarang saya semakin mengerti frasa ini!
Steven A. Lowe
5
@ [Steven A. Lowe] Pada dasarnya dikatakan bahwa jika Anda "melanggar aturan" dengan subkunci, harus ada aturan umum yang harus dilanggar, ada aturan yang ada. mis. Dari Tautan: ("Jika kita memiliki pernyataan seperti 'entri gratis pada hari Minggu', kita dapat dengan wajar mengasumsikan bahwa, sebagai aturan umum, entri dikenakan biaya.")
Fry
9

Kursor cenderung digunakan oleh pengembang SQL awal di tempat di mana operasi berbasis set akan lebih baik. Terutama ketika orang belajar SQL setelah mempelajari bahasa pemrograman tradisional, mentalitas "iterate over this records" cenderung membuat orang menggunakan kursor secara tidak tepat.

Kebanyakan buku SQL yang serius mencakup bab yang memerintahkan penggunaan kursor; yang ditulis dengan baik memperjelas bahwa kursor memiliki tempat mereka tetapi tidak boleh digunakan untuk operasi berbasis set.

Jelas ada situasi di mana kursor adalah pilihan yang benar, atau setidaknya pilihan yang benar.

davidcl
sumber
9

Pengoptimal sering tidak dapat menggunakan aljabar relasional untuk mengubah masalah ketika metode kursor digunakan. Seringkali kursor adalah cara yang bagus untuk menyelesaikan masalah, tetapi SQL adalah bahasa deklaratif, dan ada banyak informasi dalam database, dari kendala, hingga statistik dan indeks yang berarti bahwa pengoptimal memiliki banyak pilihan untuk menyelesaikan masalah. masalah, sedangkan kursor secara eksplisit mengarahkan solusi.

Cade Roux
sumber
8

Dalam Oracle PL / SQL kursor tidak akan menghasilkan kunci tabel dan dimungkinkan untuk menggunakan pengumpulan massal / pengambilan massal.

Di Oracle 10 kursor implisit yang sering digunakan

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

secara implisit mengambil 100 baris sekaligus. Pengumpulan massal / pengambilan massal secara eksplisit juga dimungkinkan.

Namun PL / SQL kursor adalah sesuatu yang terakhir, gunakan ketika Anda tidak dapat memecahkan masalah dengan set-based SQL.

Alasan lain adalah paralelisasi, lebih mudah bagi database untuk memparalelkan pernyataan berbasis set besar daripada kode imperatif baris demi baris. Ini adalah alasan yang sama mengapa pemrograman fungsional menjadi lebih dan lebih populer (Haskell, F #, Lisp, C # LINQ, MapReduce ...), pemrograman fungsional membuat paralelisasi lebih mudah. Jumlah CPU per komputer meningkat sehingga paralelisasi semakin menjadi masalah.

tuinstoel
sumber
6

Secara umum, karena pada basis data relasional, kinerja kode menggunakan kursor adalah urutan besarnya lebih buruk daripada operasi berbasis set.

Charles Bretana
sumber
apakah Anda memiliki tolok ukur atau referensi untuk ini? saya belum melihat adanya penurunan kinerja drastis seperti itu ... tapi mungkin tabel saya tidak memiliki cukup baris untuk itu penting (satu juta atau kurang, biasanya)?
Steven A. Lowe
oh tunggu, saya mengerti maksud Anda - tetapi saya tidak akan pernah menganjurkan menggunakan kursor sebelum operasi ditetapkan, hanya saja tidak akan ekstrem untuk menghindari kursor
Steven A. Lowe
3
Saya ingat pertama kali saya melakukan SQL, Kami harus mengimpor file data harian 50k dari mainframe ke dalam database SQL Server ... Saya menggunakan kursor, dan menemukan, bahwa impor membutuhkan waktu sekitar 26 jam menggunakan kursor .. Ketika saya mengubah ke operasi berbasis set, prosesnya memakan waktu 20 menit.
Charles Bretana
6

Jawaban di atas belum cukup menekankan pentingnya mengunci. Saya bukan penggemar kursor karena mereka sering menghasilkan kunci level meja.

Richard T
sumber
1
ya terima kasih! Tanpa opsi untuk mencegahnya (baca saja, hanya maju, dll) mereka pasti akan, seperti halnya setiap operasi (sql server) yang mulai menempati beberapa baris dan kemudian beberapa halaman baris.
Steven A. Lowe
?? Itu masalah dengan strategi penguncian Anda BUKAN kursor. Bahkan pernyataan SELECT akan menambahkan kunci baca.
Adam
3

Untuk apa nilainya, saya telah membaca bahwa "satu" tempat kursor akan melakukan rekan set-nya berada dalam total berjalan. Di atas sebuah meja kecil, kecepatan merangkum baris di atas urutan dengan kolom lebih menyukai operasi berbasis set tetapi ketika tabel bertambah dalam ukuran baris, kursor akan menjadi lebih cepat karena hanya dapat membawa nilai total berjalan ke lintasan berikutnya dari tabel. lingkaran. Sekarang di mana Anda harus melakukan total berjalan adalah argumen yang berbeda ...

Eric Sabine
sumber
1
Jika Anda maksud dengan "menjalankan total" suatu agregasi dari beberapa jenis (min, maks, jumlah), setiap DBMS yang kompeten akan mengalahkan solusi sisi klien, berbasis kursor, jika hanya karena fungsinya dijalankan di engine dan tidak ada klien <--> overhead server. Mungkin SQL Server tidak kompeten?
Richard T
1
@ [Richard T]: kita sedang membahas kursor sisi server, seperti dalam prosedur tersimpan, bukan kursor sisi klien; maaf bila membingungkan!
Steven A. Lowe
2

Di luar masalah kinerja (bukan), saya pikir kegagalan kursor terbesar adalah mereka menyakitkan untuk di-debug. Terutama dibandingkan dengan kode di sebagian besar aplikasi klien di mana debugging cenderung relatif mudah dan fitur bahasa cenderung lebih mudah. Bahkan, saya berpendapat bahwa hampir semua yang dilakukan seseorang dalam SQL dengan kursor mungkin harus terjadi di aplikasi klien.

Wyatt Barnett
sumber
2
SQL menyakitkan untuk di-debug, bahkan tanpa kursor. Alat bantu langkah-langkah MS SQL di Visual Studio sepertinya tidak menyukai saya (mereka banyak menggantung, atau tidak tersandung breakpoints sama sekali), jadi saya biasanya diringkas menjadi pernyataan PRINT ;-)
Steven A. Lowe
1

Bisakah Anda memposting contoh kursor atau tautan ke pertanyaan? Mungkin ada cara yang lebih baik daripada CTE rekursif.

Selain komentar lain, kursor bila digunakan dengan tidak benar (yang sering) menyebabkan kunci halaman / baris yang tidak perlu.

Gordon Bell
sumber
1
ada cara yang lebih baik - kursor freakin ';-)
Steven A. Lowe
1

Anda mungkin dapat menyimpulkan pertanyaan Anda setelah paragraf kedua, daripada memanggil orang-orang "gila" hanya karena mereka memiliki sudut pandang yang berbeda dari yang Anda lakukan dan jika tidak mencoba mengejek para profesional yang mungkin memiliki alasan yang sangat baik untuk merasakan cara mereka melakukannya.

Mengenai pertanyaan Anda, walaupun pasti ada situasi di mana kursor dapat dipanggil, menurut pengalaman saya pengembang memutuskan bahwa kursor "harus" digunakan JAUH lebih sering daripada yang sebenarnya terjadi. Peluang seseorang melakukan kesalahan di sisi terlalu banyak menggunakan kursor vs tidak menggunakannya ketika mereka seharusnya JAUH lebih tinggi menurut saya.

Tom H
sumber
8
tolong baca lebih lanjut, Tom - frasa yang tepat adalah "kebencian gila"; "benci" adalah objek dari kata sifat "gila", bukan "manusia". Bahasa Inggris kadang-kadang bisa agak sulit ;-)
Steven A. Lowe
0

pada dasarnya 2 blok kode yang melakukan hal yang sama. mungkin itu contoh yang agak aneh tapi itu membuktikan intinya. 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

pembaruan tunggal membutuhkan 156 ms sementara kursor mengambil 2016 ms.

Mladen Prajdic
sumber
3
baik ya, ini membuktikan bahwa ini adalah cara yang sangat bodoh untuk menggunakan kursor! tetapi bagaimana jika pembaruan setiap baris tergantung pada nilai baris sebelumnya dalam urutan tanggal?
Steven A. Lowe
BEGIN TRAN SELECT TOP 1 baseval DARI tabel ORDER BY timestamp DESC INSERT table (bidang) VALUES (vals, termasuk nilai turunan dari catatan sebelumnya) COMMIT TRAN
dkretz
@doofledorfer: yang akan menyisipkan satu baris berdasarkan baris terakhir berdasarkan tanggal, tidak memperbarui setiap baris dengan nilai dari baris sebelumnya dalam urutan tanggal
Steven A. Lowe
Untuk benar-benar menggunakan kursor, Anda harus menggunakan WHERE SAAT INI dalam pembaruan
erikkallen