Apakah ada perbedaan kinerja antara CTE, Sub-Query, Tabel Sementara atau Tabel Variabel?

222

Dalam pertanyaan SO yang sangat baik ini , perbedaan antara CTEdan sub-queriesdibahas.

Saya ingin secara khusus bertanya:

Dalam keadaan apa masing-masing berikut ini lebih efisien / lebih cepat?

  • CTE
  • Sub-Permintaan
  • Meja Sementara
  • Variabel tabel

Secara tradisional, saya telah menggunakan banyak temp tablesdalam pengembangan stored procedures- karena mereka tampaknya lebih mudah dibaca daripada banyak sub-pertanyaan yang saling terkait.

Non-recursive CTEs merangkum set data dengan sangat baik, dan sangat mudah dibaca, tetapi adakah keadaan khusus di mana orang bisa mengatakan mereka akan selalu berkinerja lebih baik? atau apakah harus selalu mengutak-atik pilihan yang berbeda untuk menemukan solusi yang paling efisien?


EDIT

Saya baru-baru ini diberitahu bahwa dalam hal efisiensi, tabel sementara adalah pilihan pertama yang baik karena memiliki histogram terkait yaitu statistik.

whytheq
sumber
4
Jawaban umum: itu tergantung. Dan itu tergantung pada beberapa faktor, pernyataan umum apa pun kemungkinan salah - dalam beberapa situasi. Pada dasarnya: Anda perlu menguji dan mengukur - lihat mana yang paling cocok untuk Anda!
marc_s
@marc_s - ok; mungkin pertanyaan ini harus ditutup karena bersifat subjektif? Pikiran Anda banyak pertanyaan SQL pada SO dapat dinilai sebagai subyektif.
whytheq
1
Ini mungkin akan ditutup sebagai terlalu luas - dan saya setuju dengan Anda - banyak hal dan topik di SQL benar-benar akan mendapatkan jawaban dari itu tergantung . Kadang-kadang seseorang dapat membuat daftar dua atau tiga kriteria untuk membuat keputusan, tetapi dengan pertanyaan Anda di sini, hampir tidak mungkin untuk memberikan saran yang baik - itu tergantung pada begitu banyak - struktur tabel Anda, data dalam tabel itu, pertanyaan yang Anda gunakan, strategi pengindeksan Anda dan banyak lagi ....
marc_s
@marc_s akan lebih baik untuk mencoba dan menyimpan - saran tentang kemungkinan pengeditan untuk OP untuk membuatnya lebih spesifik dan sempit?
whytheq
Harap perhatikan pertanyaan ini khusus untuk SQL Server. Untuk DB lain seperti postgres, CTE seringkali jauh lebih lambat daripada subqueries yang setara (lihat http://blog.2ndquadrant.com/postgresql-ctes-are-optimization-fences/ )
Jay

Jawaban:

243

SQL adalah bahasa deklaratif, bukan bahasa prosedural. Artinya, Anda membangun pernyataan SQL untuk menggambarkan hasil yang Anda inginkan. Anda tidak memberi tahu mesin SQL cara melakukan pekerjaan.

Sebagai aturan umum, sebaiknya mesin SQL dan pengoptimal SQL menemukan rencana kueri terbaik. Ada banyak upaya orang-tahun yang dikembangkan untuk mengembangkan mesin SQL, jadi biarkan para insinyur melakukan apa yang mereka tahu bagaimana melakukannya.

Tentu saja, ada situasi di mana rencana kueri tidak optimal. Kemudian Anda ingin menggunakan petunjuk kueri, menyusun ulang kueri, memperbarui statistik, menggunakan tabel sementara, menambahkan indeks, dan sebagainya untuk mendapatkan kinerja yang lebih baik.

Adapun pertanyaan Anda. Secara teori, kinerja CTE dan subkueri harus sama karena keduanya memberikan informasi yang sama kepada pengoptimal kueri. Satu perbedaan adalah bahwa CTE yang digunakan lebih dari satu kali dapat dengan mudah diidentifikasi dan dihitung satu kali. Hasilnya kemudian dapat disimpan dan dibaca beberapa kali. Sayangnya, SQL Server tampaknya tidak mengambil keuntungan dari metode optimasi dasar ini (Anda mungkin menyebut ini penghapusan subquery umum).

Tabel sementara adalah masalah yang berbeda, karena Anda memberikan lebih banyak panduan tentang bagaimana kueri harus dijalankan. Satu perbedaan utama adalah bahwa pengoptimal dapat menggunakan statistik dari tabel sementara untuk membuat rencana kueri. Ini dapat menghasilkan keuntungan kinerja. Juga, jika Anda memiliki CTE (subquery) rumit yang digunakan lebih dari satu kali, maka menyimpannya dalam tabel sementara akan sering memberikan peningkatan kinerja. Permintaan dieksekusi hanya sekali.

Jawaban untuk pertanyaan Anda adalah bahwa Anda perlu bermain-main untuk mendapatkan kinerja yang Anda harapkan, terutama untuk permintaan kompleks yang dijalankan secara teratur. Di dunia yang ideal, optimizer kueri akan menemukan jalur eksekusi yang sempurna. Meskipun sering demikian, Anda mungkin dapat menemukan cara untuk mendapatkan kinerja yang lebih baik.

Gordon Linoff
sumber
11
Beberapa Microsoft Research tentang kemungkinan perbaikan di masa mendatang di area ini adalah dalam publikasi "Eksploitasi yang Efisien dari Subekspresi yang Mirip untuk Pemrosesan Query" Tersedia dari sini
Martin Smith
3
Mengingat bahwa makalah itu disajikan pada tahun 2007, ada ide apakah mereka telah memasukkannya ke dalam SQL Server 2012?
Gordon Linoff
3
Jawaban yang bagus! Hanya untuk menekankan: SQL adalah bahasa deklaratif, dan kami tidak mengontrol BAGAIMANA data ditarik. Oleh karena itu, kinerja / kecepatan bervariasi dari permintaan ke permintaan.
Simcha Khabinsky
2
@RGS. . . Indeks pada tabel sementara pasti meningkatkan kueri yang dapat memanfaatkan indeks tersebut - seperti halnya indeks pada tabel permanen. Tapi, jika Anda mewujudkan subquery sebagai tabel sementara, Anda mungkin kehilangan keuntungan dari indeks pada tabel asli.
Gordon Linoff
2
@RGS. . . Ketika mesin database mematerialisasi subquery / CTE dalam menjalankan eksekusi query yang kompleks, itu tidak menambah indeks pada materialisasi. Anda bisa melakukan ini secara manual menggunakan tabel sementara.
Gordon Linoff
77

Tidak ada aturan. Saya menemukan CTE lebih mudah dibaca, dan menggunakannya kecuali mereka menunjukkan beberapa masalah kinerja, dalam hal ini saya menyelidiki masalah yang sebenarnya daripada menebak bahwa CTE adalah masalahnya dan mencoba untuk menulis ulang menggunakan pendekatan yang berbeda. Biasanya ada lebih banyak masalah daripada cara saya memilih untuk menyatakan secara terbuka maksud saya dengan kueri.

Tentu saja ada kasus ketika Anda dapat menguraikan CTE atau menghapus subqueries dan menggantinya dengan tabel #temp dan mengurangi durasi. Ini dapat disebabkan oleh berbagai hal, seperti statistik basi, ketidakmampuan untuk bahkan mendapatkan statistik yang akurat (misalnya bergabung dengan fungsi bernilai tabel), paralelisme, atau bahkan ketidakmampuan untuk menghasilkan rencana yang optimal karena kompleksitas kueri ( dalam hal ini memecahnya dapat memberikan pengoptimal kesempatan bertarung). Tetapi ada juga kasus di mana I / O yang terlibat dengan membuat tabel #temp dapat melebihi aspek kinerja lainnya yang dapat membuat bentuk rencana tertentu menggunakan CTE menjadi kurang menarik.

Sejujurnya, ada terlalu banyak variabel untuk memberikan jawaban yang "benar" untuk pertanyaan Anda. Tidak ada cara yang dapat diprediksi untuk mengetahui kapan kueri dapat memberi tip dalam mendukung satu pendekatan atau yang lain - cukup ketahui bahwa, secara teori, semantik yang sama untuk CTE atau satu subquery tunggal harus mengeksekusi yang sama persis. Saya pikir pertanyaan Anda akan lebih berharga jika Anda menyajikan beberapa kasus di mana ini tidak benar - mungkin Anda telah menemukan batasan dalam pengoptimal (atau menemukan yang dikenal), atau mungkin bahwa pertanyaan Anda tidak setara secara semantik. atau yang mengandung elemen yang menghalangi optimasi.

Jadi saya sarankan menulis kueri dengan cara yang tampaknya paling alami bagi Anda, dan hanya menyimpang ketika Anda menemukan masalah kinerja aktual yang dimiliki pengoptimal. Secara pribadi saya memberi peringkat mereka CTE, kemudian subquery, dengan tabel #temp menjadi pilihan terakhir.

Aaron Bertrand
sumber
4
+1 ternyata menjadi pertanyaan yang cukup subjektif; Saya harap itu tidak ditutup karena terlalu kabur karena jawaban sejauh ini informatif. Saya menyadari :-) Anda tidak suka ketika pertanyaan berubah tetapi apakah Anda punya saran untuk mempersempit pertanyaan di OP?
whytheq
2
Saya pikir pertanyaan ini baik-baik saja, Anda akan melihat belum ada satu suara untuk ditutup, tetapi jika jawaban mulai berkeliaran liar mungkin akan ditutup. Seperti yang saya sarankan dalam jawaban saya, jika Anda memiliki kasus tertentu di mana Anda melihat perbedaan besar antara CTE dan subquery, mulailah pertanyaan baru dengan pertanyaan aktual dan rencana eksekusi (dan mungkin lebih cocok untuk dba.se ) . Hanya menyadari bahwa jawaban untuk bantuan dengan yang permintaan mungkin tidak menjadi jawaban yang sama untuk pertanyaan yang berbeda dengan skenario yang sama.
Aaron Bertrand
Tepat di bawah pertanyaan Anda ada tautan link / edit / close / flag- jika ada suara untuk menutup pertanyaan, Anda akan melihat di close (n)mana nmewakili jumlah pengguna yang memilih untuk menutup pertanyaan Anda. Jika Anda mengklik tautan itu, Anda akan melihat alasan para pengguna itu dipilih.
Aaron Bertrand
@whytheq juga melihat posting blog terbaru ini oleh Bob Beauchemin . Ini tidak memperlakukan CTE vs subquery secara khusus tetapi jenis konsep yang sama berlaku: jika Anda memilih pola yang tidak intuitif untuk alasan kinerja, dokumentasikan omong kosong itu dan kunjungi kembali untuk memastikan bahwa kekhasan yang Anda temukan masih nyata. Saya bahkan mungkin menyarankan untuk membiarkan versi yang lebih alami dari kueri dikomentari, kecuali jika Anda memiliki sistem kontrol sumber yang dapat diandalkan yang memegang versi sebelumnya.
Aaron Bertrand
1
Tautan tetap di atas: sqlskills.com/blogs/bobb/…
ADJenks
19

#temp dibuat dan CTE tidak.

CTE hanyalah sintaks jadi secara teori itu hanya subquery. Itu dieksekusi. #empaan terwujud. Jadi CTE yang mahal dalam suatu join yang dieksekusi berkali-kali mungkin lebih baik dalam #temp. Di sisi lain jika itu adalah evaluasi yang mudah yang tidak dieksekusi tetapi beberapa kali maka tidak sebanding dengan overhead #temp.

Beberapa orang di SO yang tidak suka variabel tabel tapi saya suka mereka karena terwujud dan lebih cepat untuk membuat daripada #temp. Ada kalanya optimizer kueri lebih baik dengan #temp dibandingkan dengan variabel tabel.

Kemampuan untuk membuat PK pada variabel #temp atau tabel memberi pengoptimal kueri lebih banyak informasi daripada CTE (karena Anda tidak dapat mendeklarasikan PK pada CTE).

paparazzo
sumber
apa akronim "TVP" ... sesuatu yang mirip dengan #temp?
whytheq
TVP menjadi istilah umum, karena kedengarannya mengesankan (bagi sebagian orang). Singkatnya, TVP adalah tabel yang diteruskan sebagai parameter. Siapa pun yang telah menggunakan variabel Tabel akan merasa betah dengannya.
WonderWorker
1
PERINGATAN - TVP tidak memiliki rencana eksekusi! Jangan gunakan TVP untuk hal lain selain yang paling sederhana dari daftar pencarian pendek. Jika Anda melakukan gabungan, penyisipan, atau pembaruan yang rumit, Anda dapat mengalami masalah optimisasi besar-besaran. Percayalah, saya sudah terbakar oleh ini.
Heliac
12

Hanya 2 hal yang saya pikir membuatnya SELALU lebih baik menggunakan # Temp Table daripada CTE adalah:

  1. Anda tidak bisa meletakkan kunci utama pada CTE sehingga data yang diakses oleh CTE harus melewati masing-masing indeks dalam tabel CTE daripada hanya mengakses PK atau Indeks di tabel temp.

  2. Karena Anda tidak dapat menambahkan kendala, indeks, dan kunci utama ke CTE, mereka lebih rentan terhadap bug yang masuk dan data yang buruk.


-Ditunjuki kapan kemarin

Berikut adalah contoh di mana kendala #table dapat mencegah data buruk yang tidak terjadi di CTE

DECLARE @BadData TABLE ( 
                       ThisID int
                     , ThatID int );
INSERT INTO @BadData
       ( ThisID
       , ThatID
       ) 
VALUES
       ( 1, 1 ),
       ( 1, 2 ),
       ( 2, 2 ),
       ( 1, 1 );

IF OBJECT_ID('tempdb..#This') IS NOT NULL
    DROP TABLE #This;
CREATE TABLE #This ( 
             ThisID int NOT NULL
           , ThatID int NOT NULL
                        UNIQUE(ThisID, ThatID) );
INSERT INTO #This
SELECT * FROM @BadData;
WITH This_CTE
     AS (SELECT *
           FROM @BadData)
     SELECT *
       FROM This_CTE;
ShanksPranks
sumber
3
ALWAYSagak terlalu jauh tapi terima kasih atas jawabannya. Dalam hal keterbacaan penggunaan CTE bisa menjadi hal yang baik.
whytheq
3
Saya tidak mengerti poin kedua Anda sama sekali. Cara saya melihatnya, permintaan mendefinisikan CTE adalah analog dengan kendala yang Anda letakkan pada tabel temp, mencatat bahwa yang pertama dapat terdiri dari predikat kompleks yang sewenang-wenang sedangkan yang terakhir jauh lebih terbatas (misalnya CHECKkendala yang merujuk pada beberapa baris / tabel adalah tidak diizinkan). Bisakah Anda memposting contoh di mana CTE menunjukkan bug yang tidak sebanding dengan tabel temp?
onedaywhen