Pengantar
Mundur khusus untuk operator di sisi dalam loop bersarang yang bergabung atau diterapkan. Idenya adalah untuk menggunakan kembali hasil yang dihitung sebelumnya dari bagian rencana eksekusi yang aman untuk dilakukan.
Contoh kanonik dari operator paket yang dapat memundurkan adalah Table Spool . yang malas . Ini raison d'être adalah untuk men-cache baris hasil dari subpohon rencana, kemudian memutar ulang baris tersebut pada iterasi berikutnya jika ada parameter loop yang berkorelasi tidak berubah. Memutar ulang baris bisa lebih murah daripada menjalankan kembali subpohon yang menghasilkannya. Untuk latar belakang lebih lanjut tentang gulungan kinerja ini lihat artikel saya sebelumnya.
Dokumentasi mengatakan hanya operator berikut yang dapat memundurkan:
- Kumparan Meja
- Kumpulan Hitungan Baris
- Kumpulan Indeks Tanpa Gugus
- Fungsi bernilai tabel
- Urutkan
- Kueri Jarak Jauh
- Tegaskan dan Filter operator dengan Ekspresi Startup
Tiga item pertama paling sering merupakan gulungan kinerja, meskipun mereka dapat diperkenalkan karena alasan lain (ketika mereka mungkin bersemangat dan juga malas).
Fungsi bernilai tabel gunakan variabel tabel, yang dapat digunakan untuk menyimpan dan memutar ulang hasil dalam keadaan yang sesuai. Jika Anda tertarik dengan rewind fungsi bernilai tabel, silakan lihat T &J saya di Database Administrators Stack Exchange.
Dengan itu, artikel ini secara eksklusif tentang Urutan dan kapan mereka bisa mundur.
Urutkan Putar Ulang
Mengurutkan menggunakan penyimpanan (memori dan mungkin disk jika tumpah) sehingga mereka memiliki fasilitas yang mampu menyimpan baris antara iterasi loop. Secara khusus, keluaran yang diurutkan, pada prinsipnya, dapat diputar ulang (diputar ulang).
Namun, jawaban singkat untuk pertanyaan judul, "Do Sorts Rewind?" adalah:
Ya, tetapi Anda tidak akan sering melihatnya.
Urutkan jenis
Sortir datang dalam berbagai jenis secara internal, tetapi untuk tujuan kami saat ini hanya ada dua:
- Urutan Dalam Memori (
CQScanInMemSortNew
).- Selalu ada di memori; tidak bisa tumpah ke disk.
- Menggunakan pengurutan cepat perpustakaan standar.
- Maksimum 500 baris dan dua halaman 8KB secara keseluruhan.
- Semua input harus berupa konstanta runtime. Biasanya ini berarti seluruh subpohon pengurutan harus terdiri dari hanya Pemindaian Konstan dan/atau Hitung Skalar operator.
- Hanya dapat dibedakan secara eksplisit dalam rencana eksekusi ketika showplan bertele-tele diaktifkan (trace flag 8666). Ini menambahkan properti ekstra ke Urutkan salah satunya adalah “InMemory=[0|1]”.
- Semua Jenis Lainnya.
(Kedua jenis Urut operator menyertakan Top N Sort dan Urutan Berbeda varian).
Mundurkan Perilaku
-
Urutan Dalam Memori selalu dapat memundurkan ketika aman. Jika tidak ada parameter loop yang berkorelasi, atau nilai parameter tidak berubah dari iterasi sebelumnya, jenis pengurutan ini dapat memutar ulang data yang disimpannya alih-alih mengeksekusi ulang operator di bawahnya dalam rencana eksekusi.
-
Urutan Tanpa Memori dapat memundurkan saat aman, tetapi hanya jika Urutkan operator berisi paling banyak satu baris . Harap perhatikan Urutkan input dapat memberikan satu baris pada beberapa iterasi, tetapi tidak pada yang lain. Perilaku runtime karena itu dapat menjadi campuran kompleks dari rewinds dan rebinds. Ini sepenuhnya tergantung pada berapa banyak baris yang disediakan untuk Urutkan pada setiap iterasi saat runtime. Anda biasanya tidak dapat memprediksi apa yang Urutkan akan dilakukan pada setiap iterasi dengan memeriksa rencana eksekusi.
Kata “aman” dalam uraian di atas berarti:Tidak terjadi perubahan parameter, atau tidak ada operator di bawah Urutkan memiliki ketergantungan nilai yang diubah.
Catatan penting tentang rencana eksekusi
Rencana eksekusi tidak selalu melaporkan rewinds (dan rebinds) dengan benar untuk Sort operator. Operator akan melaporkan rewind jika ada parameter yang berkorelasi tidak berubah, dan rebind sebaliknya.
Untuk pengurutan non-dalam-memori (jauh dan paling umum), rewind yang dilaporkan hanya akan benar-benar memutar ulang hasil pengurutan yang disimpan jika ada paling banyak satu baris dalam buffer keluaran pengurutan. Jika tidak, pengurutan akan melaporkan mundur, tetapi subpohon masih akan sepenuhnya dijalankan ulang (rebind).
Untuk memeriksa berapa banyak rewind yang dilaporkan adalah rewind yang sebenarnya, periksa Jumlah Eksekusi properti pada operator di bawah Urutkan .
Sejarah dan penjelasan saya
Urutkan perilaku mundur operator mungkin tampak aneh, tetapi sudah seperti ini dari (setidaknya) SQL Server 2000 ke SQL Server 2019 inklusif (serta Azure SQL Database). Saya belum dapat menemukan penjelasan atau dokumentasi resmi tentang hal itu.
Pandangan pribadi saya adalah Urutkan rewinds cukup mahal karena mesin penyortiran yang mendasarinya, termasuk fasilitas tumpahan, dan penggunaan sistem transaksi di tempdb .
Dalam kebanyakan kasus, pengoptimal akan lebih baik memperkenalkan spool kinerja explicit eksplisit ketika mendeteksi kemungkinan duplikat parameter loop berkorelasi. Spool adalah cara paling murah untuk menyimpan sebagian hasil.
Itu mungkin yang memutar ulang Urut hasilnya hanya akan lebih hemat biaya daripada Spool saat Urut berisi paling banyak satu baris. Lagi pula, pengurutan satu baris (atau tidak ada baris!) sebenarnya tidak melibatkan pengurutan sama sekali, sehingga banyak overhead yang dapat dihindari.
Spekulasi murni, tapi pasti ada yang bertanya, jadi begitulah.
Demo 1:Pemutaran Ulang Tidak Akurat
Contoh pertama ini menampilkan dua variabel tabel. Yang pertama berisi tiga nilai yang digandakan tiga kali di kolom c1
. Tabel kedua berisi dua baris untuk setiap kecocokan pada c2 = c1
. Dua baris yang cocok dibedakan dengan nilai di kolom c3
.
Tugasnya adalah mengembalikan baris dari tabel kedua dengan c3
tertinggi nilai untuk setiap kecocokan pada c1 = c2
. Kodenya mungkin lebih jelas dari penjelasan saya:
DECLARE @T1 table (c1 integer NOT NULL INDEX i); DECLARE @T2 table (c2 integer NOT NULL, c3 integer NOT NULL); INSERT @T1 (c1) VALUES (1), (1), (1), (2), (2), (2), (3), (3), (3); INSERT @T2 (c2, c3) VALUES (1, 1), (1, 2), (2, 3), (2, 4), (3, 5), (3, 6); SELECT T1.c1, CA.c2, CA.c3 FROM @T1 AS T1 CROSS APPLY ( SELECT TOP (1) T2.c2, T2.c3 FROM @T2 AS T2 WHERE T2.c2 = T1.c1 ORDER BY T2.c3 DESC ) AS CA ORDER BY T1.c1 ASC OPTION (NO_PERFORMANCE_SPOOL);
NO_PERFORMANCE_SPOOL
petunjuk ada untuk mencegah pengoptimal memperkenalkan spool kinerja. Ini dapat terjadi dengan variabel tabel ketika mis. trace flag 2453 diaktifkan, atau kompilasi ditangguhkan variabel tabel tersedia, sehingga pengoptimal dapat melihat kardinalitas sebenarnya dari variabel tabel (tetapi bukan distribusi nilai).
Hasil kueri menunjukkan c2
dan c3
nilai yang dikembalikan sama untuk setiap c1
yang berbeda nilai:
Rencana eksekusi sebenarnya untuk kueri adalah:
c1
nilai, disajikan secara berurutan, cocok dengan iterasi sebelumnya 6 kali, dan berubah 3 kali. Urutkan melaporkan ini sebagai 6 pemutaran ulang dan 3 pemutaran ulang.
Jika ini benar, Scan Tabel hanya akan mengeksekusi 3 kali. Urutkan akan memutar ulang (memundurkan) hasilnya pada 6 kesempatan lainnya.
Seperti itu, kita bisa melihat Scan Tabel dieksekusi 9 kali, sekali untuk setiap baris dari tabel @T1
. Tidak ada pemutaran ulang yang terjadi di sini .
Demo 2:Urutkan Mundur
Contoh sebelumnya tidak mengizinkan Urutkan untuk mundur karena (a) ini bukan Urutan Dalam Memori; dan (b) pada setiap iterasi dari loop, Urutkan berisi dua baris. Plan Explorer menunjukkan total 18 baris dari Scan Tabel , dua baris pada masing-masing dari 9 iterasi.
Mari kita ubah contohnya sekarang sehingga hanya ada satu baris dalam tabel @T2
untuk setiap baris yang cocok dari @T1
:
DECLARE @T1 table (c1 integer NOT NULL INDEX i); DECLARE @T2 table (c2 integer NOT NULL, c3 integer NOT NULL); INSERT @T1 (c1) VALUES (1), (1), (1), (2), (2), (2), (3), (3), (3); -- Only one matching row per iteration now INSERT @T2 (c2, c3) VALUES --(1, 1), (1, 2), --(2, 3), (2, 4), --(3, 5), (3, 6); SELECT T1.c1, CA.c2, CA.c3 FROM @T1 AS T1 CROSS APPLY ( SELECT TOP (1) T2.c2, T2.c3 FROM @T2 AS T2 WHERE T2.c2 = T1.c1 ORDER BY T2.c3 DESC ) AS CA ORDER BY T1.c1 ASC OPTION (NO_PERFORMANCE_SPOOL);
Hasilnya sama seperti yang ditunjukkan sebelumnya karena kami menyimpan baris yang cocok yang diurutkan paling tinggi pada kolom c3
. Rencana eksekusi juga sangat mirip, tetapi dengan perbedaan penting:
Dengan satu baris di Urutkan pada satu waktu, ia dapat memundurkan ketika parameter berkorelasi c1
tidak berubah. Pemindaian Tabel hanya dieksekusi 3 kali sebagai hasilnya.
Perhatikan Urutkan menghasilkan lebih banyak baris (9) daripada yang diterimanya (3). Ini adalah indikasi yang baik bahwa Urutkan telah berhasil men-cache set hasil satu kali atau lebih – rewind berhasil.
Demo 3:Tidak Memutar Ulang
Saya sebutkan sebelumnya bahwa Urutkan yang tidak ada dalam memori dapat memundurkan jika berisi paling banyak satu baris.
Untuk melihatnya beraksi dengan nol baris , kita ubah menjadi OUTER APPLY
dan jangan letakkan baris apa pun di tabel @T2
. Untuk alasan yang akan segera terlihat, kami juga akan berhenti memproyeksikan kolom c2
:
DECLARE @T1 table (c1 integer NOT NULL INDEX i); DECLARE @T2 table (c2 integer NOT NULL, c3 integer NOT NULL); INSERT @T1 (c1) VALUES (1), (1), (1), (2), (2), (2), (3), (3), (3); -- No rows added to table @T2 -- No longer projecting c2 SELECT T1.c1, --CA.c2, CA.c3 FROM @T1 AS T1 OUTER APPLY ( SELECT TOP (1) --T2.c2, T2.c3 FROM @T2 AS T2 WHERE T2.c2 = T1.c1 ORDER BY T2.c3 DESC ) AS CA ORDER BY T1.c1 ASC OPTION (NO_PERFORMANCE_SPOOL);
Hasilnya sekarang memiliki NULL
di kolom c3
seperti yang diharapkan:
Rencana eksekusi adalah:
Urutkan dapat memundurkan tanpa baris dalam buffernya, jadi Scan Tabel hanya dieksekusi 3 kali, setiap kali kolom c1
nilai yang diubah.
Demo 4:Putar Ulang Maksimum!
Seperti operator lain yang mendukung rewinds, Sort hanya akan mengembalikan subpohonnya jika parameter yang berkorelasi telah berubah dan subpohon bergantung pada nilai itu entah bagaimana.
Memulihkan kolom c2
proyeksi ke demo 3 akan menunjukkan ini dalam tindakan:
DECLARE @T1 table (c1 integer NOT NULL INDEX i); DECLARE @T2 table (c2 integer NOT NULL, c3 integer NOT NULL); INSERT @T1 (c1) VALUES (1), (1), (1), (2), (2), (2), (3), (3), (3); -- Still no rows in @T2 -- Column c2 is back! SELECT T1.c1, CA.c2, CA.c3 FROM @T1 AS T1 OUTER APPLY ( SELECT TOP (1) T2.c2, T2.c3 FROM @T2 AS T2 WHERE T2.c2 = T1.c1 ORDER BY T2.c3 DESC ) AS CA ORDER BY T1.c1 ASC OPTION (NO_PERFORMANCE_SPOOL);
Hasilnya sekarang menunjukkan dua NULL
kolom tentu saja:
Rencana eksekusi sangat berbeda:
Kali ini, Filter berisi centang T2.c2 = T1.c1
, membuat Scan Tabel mandiri dari nilai saat ini dari parameter berkorelasi c1
. Urutkan dapat mundur 8 kali dengan aman, artinya pemindaian hanya dilakukan sekali .
Demo 5:Sortir Dalam Memori
Contoh berikutnya menunjukkan Urutkan . Dalam Memori operator:
DECLARE @T table (v integer NOT NULL); INSERT @T (v) VALUES (1), (2), (3), (4), (5), (6); SELECT T.v, OA.i FROM @T AS T OUTER APPLY ( SELECT TOP (1) X.i FROM ( VALUES (REPLICATE('Z', 1390)), ('0'), ('1'), ('2'), ('3'), ('4'), ('5'), ('6'), ('7'), ('8'), ('9') ) AS X (i) ORDER BY NEWID() ) AS OA OPTION (NO_PERFORMANCE_SPOOL);
Hasil yang Anda dapatkan akan bervariasi dari satu eksekusi ke eksekusi lainnya, tetapi berikut ini contohnya:
Yang menarik adalah nilai pada kolom i
akan selalu sama — meskipun ORDER BY NEWID()
klausa.
Anda mungkin sudah menebak alasannya adalah Urutkan hasil caching (memutar ulang). Rencana eksekusi menunjukkan Pemindaian Konstan mengeksekusi hanya sekali, menghasilkan total 11 baris:
Urutkan . ini hanya memiliki Compute Scalar dan Pemindaian Konstan operator pada inputnya sehingga merupakan In Memory Sort . Ingat, ini tidak terbatas pada paling banyak satu baris — mereka dapat menampung 500 baris dan 16 KB.
Seperti disebutkan sebelumnya, tidak mungkin untuk secara eksplisit melihat apakah Urutkan adalah Dalam Memori atau tidak dengan memeriksa rencana eksekusi reguler. Kami membutuhkan output showplan verbose , diaktifkan dengan tanda jejak tidak berdokumen 8666. Dengan mengaktifkannya, properti operator tambahan akan muncul:
Jika tidak praktis untuk menggunakan tanda jejak tidak berdokumen, Anda dapat menyimpulkan bahwa Urutkan adalah "InMemory" dengan Fraksi Memori Masukan menjadi nol, dan Penggunaan Memori elemen tidak tersedia di showplan pasca-eksekusi (pada versi SQL Server yang mendukung informasi tersebut).
Kembali ke rencana eksekusi:Tidak ada parameter yang berkorelasi sehingga Urutkan bebas untuk mundur 5 kali, artinya Pemindaian Konstan hanya dieksekusi sekali. Jangan ragu untuk mengubah TOP (1)
ke TOP (3)
atau apapun yang kamu suka. Pemutaran ulang berarti hasilnya akan sama (di-cache/diputar ulang) untuk setiap baris input.
Anda mungkin terganggu oleh ORDER BY NEWID()
klausa tidak mencegah rewinding. Ini memang poin yang kontroversial, tetapi sama sekali tidak terbatas pada macam-macam. Untuk diskusi yang lebih lengkap (peringatan:kemungkinan lubang kelinci) silakan lihat Q &A ini. Versi singkatnya adalah bahwa ini adalah keputusan desain produk yang disengaja, mengoptimalkan kinerja, tetapi ada rencana untuk membuat perilaku lebih intuitif dari waktu ke waktu.
Demo 6:Tanpa Sortir Dalam Memori
Ini sama dengan demo 5, kecuali string yang direplikasi lebih panjang satu karakter:
DECLARE @T table (v integer NOT NULL); INSERT @T (v) VALUES (1), (2), (3), (4), (5), (6); SELECT T.v, OA.i FROM @T AS T OUTER APPLY ( SELECT TOP (1) X.i FROM ( VALUES -- 1391 instead of 1390 (REPLICATE('Z', 1391)), ('0'), ('1'), ('2'), ('3'), ('4'), ('5'), ('6'), ('7'), ('8'), ('9') ) AS X (i) ORDER BY NEWID() ) AS OA OPTION (NO_PERFORMANCE_SPOOL);
Sekali lagi, hasilnya akan bervariasi per eksekusi, tetapi berikut adalah contohnya. Perhatikan i
nilainya sekarang tidak semuanya sama:
Karakter tambahan hanya cukup untuk mendorong perkiraan ukuran data yang diurutkan lebih dari 16KB. Ini berarti Urutan Dalam Memori tidak dapat digunakan, dan rewinds menghilang.
Rencana eksekusi adalah:
Urutkan masih melaporkan 5 mundur, tetapi Pemindaian Konstan dieksekusi 6 kali, artinya tidak ada rewind yang benar-benar terjadi. Ini menghasilkan semua 11 baris pada masing-masing dari 6 eksekusi, memberikan total 66 baris.
Ringkasan dan Pemikiran Akhir
Anda tidak akan melihat Urutkan operator benar-benar sangat sering diputar ulang, meskipun Anda akan melihatnya mengatakannya demikian cukup banyak.
Ingat, Urutkan regular biasa hanya dapat memundurkan jika aman dan ada maksimal satu baris dalam pengurutan pada saat itu. Menjadi "aman" berarti tidak ada perubahan dalam parameter korelasi loop, atau tidak ada di bawah Urut dipengaruhi oleh perubahan parameter.
Sebuah Urutan Dalam-Memori dapat beroperasi pada hingga 500 baris dan 16KB data yang bersumber dari Pemindaian Konstan dan Hitung Skalar operator saja. Ini juga hanya akan mundur ketika aman (kecuali bug produk!) tetapi tidak terbatas pada maksimum satu baris.
Ini mungkin tampak seperti detail esoteris, dan saya kira memang demikian. Jadi mengatakan, mereka telah membantu saya memahami rencana eksekusi dan menemukan peningkatan kinerja yang baik lebih dari sekali. Mungkin Anda akan menemukan informasi yang berguna suatu hari nanti.
Perhatikan Urutan yang menghasilkan lebih banyak baris daripada inputnya!
Jika Anda ingin melihat contoh yang lebih realistis dari Urutkan mundur berdasarkan demo yang diberikan Itzik Ben-Gan di bagian pertama dari Pertandingan Terdekat seri silakan lihat Pertandingan Terdekat dengan Sort Rewinds.