Ini adalah angsuran ketiga belas dan terakhir dalam seri tentang ekspresi tabel. Bulan ini saya melanjutkan diskusi yang saya mulai bulan lalu tentang inline table-valued function (iTVFs).
Bulan lalu saya menjelaskan bahwa ketika SQL Server menyejajarkan iTVF yang ditanyakan dengan konstanta sebagai input, itu menerapkan optimasi penyematan parameter secara default. Penyematan parameter berarti bahwa SQL Server mengganti referensi parameter dalam kueri dengan nilai konstanta literal dari eksekusi saat ini, dan kemudian kode dengan konstanta tersebut dioptimalkan. Proses ini memungkinkan penyederhanaan yang dapat menghasilkan rencana kueri yang lebih optimal. Bulan ini saya menguraikan topik tersebut, mencakup kasus-kasus khusus untuk penyederhanaan seperti pelipatan konstan dan pemfilteran dan pemesanan dinamis. Jika Anda memerlukan penyegaran tentang pengoptimalan penyematan parameter, baca artikel bulan lalu serta artikel luar biasa Paul White Parameter Sniffing, Embedding, dan RECOMPILE Options.
Dalam contoh saya, saya akan menggunakan database sampel yang disebut TSQLV5. Anda dapat menemukan skrip yang membuat dan mengisinya di sini dan diagram ER-nya di sini.
Melipat Konstan
Selama tahap awal pemrosesan kueri, SQL Server mengevaluasi ekspresi tertentu yang melibatkan konstanta, melipatnya ke konstanta hasil. Misalnya, ekspresi 40 + 2 dapat dilipat menjadi konstanta 42. Anda dapat menemukan aturan untuk ekspresi foldable dan nonfoldable di sini di bagian “Constant Folding and Expression Evaluation”.
Yang menarik terkait iTVF adalah berkat pengoptimalan penyematan parameter, kueri yang melibatkan iTVF tempat Anda meneruskan konstanta karena input dapat, dalam keadaan yang tepat, mendapat manfaat dari pelipatan konstan. Mengetahui aturan untuk ekspresi yang dapat dilipat dan tidak dapat dilipat dapat memengaruhi cara Anda menerapkan iTVF. Dalam beberapa kasus, dengan menerapkan perubahan yang sangat halus pada ekspresi, Anda dapat mengaktifkan rencana yang lebih optimal dengan pemanfaatan pengindeksan yang lebih baik.
Sebagai contoh, pertimbangkan implementasi iTVF berikut yang disebut Sales.MyOrders:
GUNAKAN TSQLV5;PERGI MEMBUAT ATAU MENGUBAH FUNGSI Sales.MyOrders ( @add AS INT, @subtract AS INT )RETURNS TABLEASRETURN SELECT orderid + @add - @subtract AS myorderid, orderdate, custid, empid FROM Sales.Orders;GOKeluarkan kueri berikut yang melibatkan iTVF (saya akan menyebutnya sebagai Kueri 1):
PILIH myorderid, orderdate, custid, empidFROM Sales.MyOrders(1, 10248)ORDER BY myorderid;Rencana untuk Query 1 ditunjukkan pada Gambar 1.
Gambar 1:Rencana untuk Kueri 1
Indeks berkerumun PK_Orders didefinisikan dengan orderid sebagai kuncinya. Jika pelipatan konstan terjadi di sini setelah penyematan parameter, ekspresi pengurutan orderid + 1 – 10248 akan dilipat menjadi orderid – 10247. Ekspresi ini akan dianggap sebagai ekspresi pelestarian pesanan sehubungan dengan orderid, dan dengan demikian akan mengaktifkan pengoptimal untuk mengandalkan urutan indeks. Sayangnya, bukan itu masalahnya, seperti yang ditunjukkan oleh operator Sortir eksplisit dalam rencana. Jadi apa yang terjadi?
Aturan lipat konstan itu rewel. Ekspresi kolom1 + konstanta1 – konstanta2 dievaluasi dari kiri ke kanan untuk tujuan pelipatan konstan. Bagian pertama, kolom1 + konstanta1 tidak dilipat. Sebut saja ekspresi ini1. Bagian selanjutnya yang dievaluasi diperlakukan sebagai ekspresi1 – konstanta2, yang juga tidak terlipat. Tanpa pelipatan, ekspresi dalam bentuk kolom1 + konstanta1 – konstanta2 tidak dianggap mempertahankan urutan sehubungan dengan kolom1, dan oleh karena itu tidak dapat mengandalkan pengurutan indeks bahkan jika Anda memiliki indeks pendukung pada kolom1. Demikian pula, ekspresi constant1 + column1 – constant2 tidak dapat dilipat secara konstan. Namun, ekspresi constant1 – constant2 + column1 dapat dilipat. Lebih khusus lagi, bagian pertama konstanta1 – konstanta2 dilipat menjadi konstanta tunggal (sebut saja konstanta3), menghasilkan ekspresi konstanta3 + kolom1. Ekspresi ini dianggap sebagai ekspresi pelestarian pesanan sehubungan dengan kolom1. Jadi selama Anda memastikan untuk menulis ekspresi Anda menggunakan formulir terakhir, Anda dapat mengaktifkan pengoptimal untuk mengandalkan pengurutan indeks.
Pertimbangkan kueri berikut (saya akan menyebutnya sebagai Kueri 2, Kueri 3, dan Kueri 4), dan sebelum melihat rencana kueri, lihat apakah Anda dapat membedakan mana yang akan melibatkan penyortiran eksplisit dalam rencana dan mana yang tidak:
-- Query 2SELECT orderid + 1 - 10248 AS myorderid, orderdate, custid, empidFROM Sales.OrdersORDER BY myorderid; -- Query 3SELECT 1 + orderid - 10248 AS myorderid, orderdate, custid, empidFROM Sales.OrdersORDER BY myorderid; -- Query 4SELECT 1 - 10248 + orderid AS myorderid, orderdate, custid, empidFROM Sales.OrdersORDER BY myorderid;Sekarang periksa rencana untuk kueri ini seperti yang ditunjukkan pada Gambar 2.
Gambar 2:Rencana untuk Kueri 2, Kueri 3, dan Kueri 4
Periksa operator Hitung Skalar dalam tiga rencana. Hanya paket untuk Kueri 4 yang mengalami pelipatan konstan, menghasilkan ekspresi pengurutan yang dianggap mempertahankan pesanan sehubungan dengan orderid, menghindari penyortiran eksplisit.
Memahami aspek lipatan konstan ini, Anda dapat dengan mudah memperbaiki iTVF dengan mengubah ekspresi orderid + @add – @subtract menjadi @add – @subtract + orderid, seperti:
BUAT ATAU ubah FUNGSI Penjualan.Pesanan Saya ( @add AS INT, @subtract AS INT )RETURNS TABLEASRETURN SELECT @add - @subtract + orderid AS myorderid, orderdate, custid, empid FROM Sales.Orders;GOKueri fungsi lagi (saya akan menyebutnya sebagai Kueri 5):
PILIH myorderid, orderdate, custid, empidFROM Sales.MyOrders(1, 10248)ORDER BY myorderid;Rencana untuk kueri ini ditunjukkan pada Gambar 3.
Gambar 3:Rencana untuk Kueri 5
Seperti yang Anda lihat, kali ini kueri mengalami pelipatan konstan dan pengoptimal dapat mengandalkan pengurutan indeks, menghindari pengurutan eksplisit.
Saya menggunakan contoh sederhana untuk mendemonstrasikan teknik pengoptimalan ini, dan karena itu mungkin tampak sedikit dibuat-buat. Anda dapat menemukan aplikasi praktis dari teknik ini di artikel Solusi tantangan generator seri angka – Bagian 1.
Pemfilteran/Pengurutan Dinamis
Bulan lalu saya membahas perbedaan antara cara SQL Server mengoptimalkan kueri di iTVF versus kueri yang sama dalam prosedur tersimpan. SQL Server biasanya akan menerapkan optimasi penyematan parameter secara default untuk kueri yang melibatkan iTVF dengan konstanta sebagai input, tetapi mengoptimalkan bentuk kueri yang diparameterisasi dalam prosedur tersimpan. Namun, jika Anda menambahkan OPTION(RECOMPILE) ke kueri dalam prosedur tersimpan, SQL Server biasanya akan menerapkan optimasi penyematan parameter dalam kasus ini juga. Manfaat dalam kasus iTVF mencakup fakta bahwa Anda dapat melibatkannya dalam kueri, dan selama Anda meneruskan input konstan yang berulang, ada potensi untuk menggunakan kembali paket yang di-cache sebelumnya. Dengan prosedur tersimpan, Anda tidak dapat melibatkannya dalam kueri, dan jika Anda menambahkan OPTION(RECOMPILE) untuk mendapatkan pengoptimalan penyematan parameter, tidak ada kemungkinan penggunaan kembali rencana. Prosedur tersimpan memungkinkan lebih banyak fleksibilitas dalam hal elemen kode yang dapat Anda gunakan.
Mari kita lihat bagaimana semua ini dimainkan dalam tugas penyematan dan pengurutan parameter klasik. Berikut ini adalah prosedur tersimpan yang disederhanakan yang menerapkan pemfilteran dan pengurutan dinamis yang serupa dengan yang digunakan Paul dalam artikelnya:
BUAT ATAUUBAH PROSEDUR HR.GetEmpsP @lastnamepattern SEBAGAI NVARCHAR(50), @sort SEBAGAI TINYINTASSET NOCOUNT ON; PILIH empid, firstname, lastnameFROM HR.EmployeesWHERE lastname LIKE @lastnamepattern OR @lastnamepattern IS NULLORDER BY CASE WHEN @sort =1 THEN empid END, CASE WHEN @sort =2 THEN firstname END, CASE WHEN @sort =3 THEN lastname END;GOPerhatikan bahwa implementasi prosedur tersimpan saat ini tidak menyertakan OPTION(RECOMPILE) dalam kueri.
Pertimbangkan eksekusi prosedur tersimpan berikut:
EXEC HR.GetEmpsP @lastnamepattern =N'D%', @sort =3;Rencana eksekusi ini ditunjukkan pada Gambar 4.
Gambar 4:Rencana Prosedur HR.GetEmpsP
Ada indeks yang ditentukan pada kolom nama belakang. Secara teoritis, dengan input saat ini, indeks dapat bermanfaat baik untuk pemfilteran (dengan pencarian) dan pemesanan (dengan pemindaian rentang yang benar) dari kueri. Namun, karena secara default SQL Server mengoptimalkan bentuk kueri yang diparameterisasi dan tidak menerapkan penyematan parameter, SQL Server tidak menerapkan penyederhanaan yang diperlukan untuk dapat memanfaatkan indeks untuk tujuan pemfilteran dan pemesanan. Jadi, paket tersebut dapat digunakan kembali, tetapi tidak optimal.
Untuk melihat bagaimana hal-hal berubah dengan optimasi penyematan parameter, ubah kueri prosedur tersimpan dengan menambahkan OPTION(RECOMPILE), seperti:
BUAT ATAUUBAH PROSEDUR HR.GetEmpsP @lastnamepattern SEBAGAI NVARCHAR(50), @sort SEBAGAI TINYINTASSET NOCOUNT ON; PILIH empid, firstname, lastnameFROM HR.EmployeesWHERE lastname LIKE @lastnamepattern OR @lastnamepattern IS NULLORDER BY CASE WHEN @sort =1 THEN empid END, CASE WHEN @sort =2 THEN firstname END, CASE WHEN @sort =3 THEN lastname ENDOPTION(RECOMPILETION(RECOMPILETION) );PERGIJalankan prosedur tersimpan lagi dengan input yang sama yang Anda gunakan sebelumnya:
EXEC HR.GetEmpsP @lastnamepattern =N'D%', @sort =3;Rencana eksekusi ini ditunjukkan pada Gambar 5.
Gambar 5:Rencana Prosedur HR.GetEmpsP Dengan OPTION(RECOMPILE)
Seperti yang Anda lihat, berkat optimasi penyematan parameter, SQL Server dapat menyederhanakan predikat filter ke nama belakang predikat sargable LIKE N'D%', dan daftar pemesanan ke NULL, NULL, nama belakang. Kedua elemen dapat mengambil manfaat dari indeks pada nama belakang, dan oleh karena itu rencana menunjukkan pencarian dalam indeks dan tidak ada penyortiran eksplisit.
Secara teoritis, Anda berharap bisa mendapatkan penyederhanaan serupa jika Anda mengimplementasikan kueri di iTVF, dan karenanya mendapatkan manfaat pengoptimalan yang serupa, tetapi dengan kemampuan untuk menggunakan kembali paket yang di-cache saat nilai input yang sama digunakan kembali. Jadi, mari kita coba…
Berikut ini upaya untuk menerapkan kueri yang sama di iTVF (jangan jalankan kode ini dulu):
BUAT ATAU ALTER FUNGSI HR.GetEmpsF( @lastnamepattern AS NVARCHAR(50), @sort AS TINYINT)RETURNS TABLEASRETURN SELECT empid, firstname, lastname FROM HR.Employees WHERE lastname LIKE @lastnamepattern OR @lastnamepattern ADALAH NULL ORDER @sort =1 THEN empid END, CASE WHEN @sort =2 THEN firstname END, CASE WHEN @sort =3 THEN lastname END;GOSebelum Anda mencoba mengeksekusi kode ini, dapatkah Anda melihat masalah dengan implementasi ini? Ingat bahwa di awal seri ini saya menjelaskan ekspresi tabel adalah tabel. Badan tabel adalah satu set (atau multiset) baris, dan karena itu tidak memiliki urutan. Oleh karena itu, biasanya, kueri yang digunakan sebagai ekspresi tabel tidak dapat memiliki klausa ORDER BY. Memang, jika Anda mencoba menjalankan kode ini, Anda mendapatkan kesalahan berikut:
Msg 1033, Level 15, State 1, Procedure GetEmps, Line 16 [Batch Start Line 128]
Klausa ORDER BY tidak valid dalam tampilan, fungsi inline, tabel turunan, subkueri, dan ekspresi tabel umum, kecuali TOP, OFFSET atau FOR XML juga ditentukan.Tentu, seperti yang dikatakan kesalahan, SQL Server akan membuat pengecualian jika Anda menggunakan elemen pemfilteran seperti TOP atau OFFSET-FETCH, yang bergantung pada klausa ORDER BY untuk menentukan aspek pengurutan filter. Tetapi bahkan jika Anda menyertakan klausa ORDER BY dalam kueri dalam berkat pengecualian ini, Anda masih tidak mendapatkan jaminan untuk urutan hasil dalam kueri luar terhadap ekspresi tabel, kecuali jika memiliki klausa ORDER BY sendiri .
Jika Anda masih ingin mengimplementasikan kueri dalam iTVF, Anda dapat meminta kueri dalam menangani bagian pemfilteran dinamis, tetapi bukan pengurutan dinamis, seperti:
BUAT ATAU ubah FUNGSI HR.GetEmpsF( @lastnamepattern AS NVARCHAR(50))RETURNS TABLEASRETURN SELECT empid, firstname, lastname FROM HR.Employees WHERE lastname LIKE @lastnamepattern OR @lastnamepattern IS NULL;GOTentu saja, Anda dapat membuat kueri luar menangani kebutuhan pemesanan khusus apa pun, seperti dalam kode berikut (saya akan menyebutnya sebagai Kueri 6):
PILIH empid, nama depan, nama belakangFROM HR.GetEmpsF(N'D%')ORDER BY nama belakang;Rencana untuk kueri ini ditunjukkan pada Gambar 6.
Gambar 6:Rencana untuk Kueri 6
Berkat inlining dan penyematan parameter, rencana ini mirip dengan yang ditunjukkan sebelumnya untuk kueri prosedur tersimpan pada Gambar 5. Rencana tersebut secara efisien bergantung pada indeks untuk tujuan pemfilteran dan pemesanan. Namun, Anda tidak mendapatkan fleksibilitas input pemesanan dinamis seperti yang Anda miliki dengan prosedur tersimpan. Anda harus eksplisit dengan pengurutan dalam klausa ORDER BY dalam kueri terhadap fungsi tersebut.
Contoh berikut memiliki kueri terhadap fungsi tanpa pemfilteran dan tanpa persyaratan pemesanan (saya akan menyebutnya sebagai Kueri 7):
PILIH empid, nama depan, nama belakangFROM HR.GetEmpsF(NULL);Rencana untuk kueri ini ditunjukkan pada Gambar 7.
Gambar 7:Rencana untuk Kueri 7
Setelah penyisipan dan penyematan parameter, kueri disederhanakan menjadi tidak memiliki predikat filter dan tidak memiliki pengurutan, dan dioptimalkan dengan pemindaian tidak berurutan penuh dari indeks berkerumun.
Terakhir, kueri fungsi dengan N'D%' sebagai pola penyaringan nama belakang masukan dan urutkan hasilnya menurut kolom nama depan (saya akan menyebutnya sebagai Kueri 8):
SELECT empid, firstname, lastnameFROM HR.GetEmpsF(N'D%')ORDER BY firstname;Rencana untuk kueri ini ditunjukkan pada Gambar 8.
Gambar 8:Rencanakan Kueri 8
Setelah penyederhanaan, kueri hanya melibatkan predikat penyaringan nama belakang LIKE N'D%' dan nama depan elemen pengurutan. Kali ini pengoptimal memilih untuk menerapkan pemindaian tidak berurutan dari indeks berkerumun, dengan predikat sisa nama belakang LIKE N'D%', diikuti dengan penyortiran eksplisit. Ia memilih untuk tidak menerapkan pencarian dalam indeks pada nama belakang karena indeks tidak mencakup satu, tabel sangat kecil, dan pengurutan indeks tidak bermanfaat untuk kebutuhan pemesanan kueri saat ini. Juga, tidak ada indeks yang ditentukan pada kolom nama depan, jadi pengurutan eksplisit harus tetap diterapkan.
Kesimpulan
Optimalisasi penyematan parameter default dari iTVF juga dapat menghasilkan pelipatan yang konstan, memungkinkan rencana yang lebih optimal. Namun, Anda harus memperhatikan aturan pelipatan yang konstan untuk menentukan cara terbaik merumuskan ekspresi Anda.
Menerapkan logika dalam iTVF memiliki kelebihan dan kekurangan dibandingkan dengan menerapkan logika dalam prosedur tersimpan. Jika Anda tidak tertarik dengan pengoptimalan penyematan parameter, pengoptimalan kueri berparameter default dari prosedur tersimpan dapat menghasilkan cache rencana dan perilaku penggunaan kembali yang lebih optimal. Jika Anda tertarik dengan pengoptimalan penyematan parameter, Anda biasanya mendapatkannya secara default dengan iTVF. Untuk mendapatkan pengoptimalan ini dengan prosedur tersimpan, Anda perlu menambahkan opsi kueri RECOMPILE, tetapi Anda tidak akan mendapatkan penggunaan kembali paket. Setidaknya dengan iTVF, Anda bisa mendapatkan penggunaan kembali paket asalkan nilai parameter yang sama diulang. Kemudian lagi, Anda memiliki lebih sedikit fleksibilitas dengan elemen kueri yang dapat Anda gunakan di iTVF; misalnya, Anda tidak diperbolehkan memiliki klausa ORDER BY presentasi.
Kembali ke seluruh rangkaian ekspresi tabel, saya menemukan topik menjadi sangat penting bagi praktisi database. Seri yang lebih lengkap mencakup subseri pada generator seri nomor, yang diimplementasikan sebagai iTVF. Secara total, seri ini mencakup 19 bagian berikut:
- Dasar-dasar ekspresi tabel, Bagian 1
- Dasar-dasar ekspresi tabel, Bagian 2 – Tabel turunan, pertimbangan logis
- Dasar-dasar ekspresi tabel, Bagian 3 – Tabel turunan, pertimbangan pengoptimalan
- Dasar-dasar ekspresi tabel, Bagian 4 – Tabel turunan, pertimbangan pengoptimalan, lanjutan
- Dasar-dasar ekspresi tabel, Bagian 5 – CTE, pertimbangan logis
- Dasar-dasar ekspresi tabel, Bagian 6 – CTE rekursif
- Dasar-dasar ekspresi tabel, Bagian 7 – CTE, pertimbangan pengoptimalan
- Dasar-dasar ekspresi tabel, Bagian 8 – CTE, pertimbangan pengoptimalan lanjutan
- Dasar-dasar ekspresi tabel, Bagian 9 – Tampilan, dibandingkan dengan tabel turunan dan CTE
- Dasar-dasar ekspresi tabel, Bagian 10 – Tampilan, SELECT *, dan perubahan DDL
- Dasar-dasar Ekspresi Tabel, Bagian 11 – Tampilan, Pertimbangan Modifikasi
- Dasar-dasar Ekspresi Tabel, Bagian 12 – Fungsi Bernilai Tabel Sebaris
- Dasar-dasar Ekspresi Tabel, Bagian 13 – Fungsi Bernilai Tabel Sebaris, lanjutan
- Tantangannya ada! Panggilan komunitas untuk membuat generator seri nomor tercepat
- Solusi tantangan generator seri nomor – Bagian 1
- Solusi tantangan generator seri nomor – Bagian 2
- Solusi tantangan generator seri nomor – Bagian 3
- Solusi tantangan generator seri nomor – Bagian 4
- Solusi tantangan generator seri angka – Bagian 5