Setiap kali saya mengalami jenis pertanyaan ini, saya selalu bertanya-tanya bagaimana SQL Server akan menyelesaikannya. Jika saya menjalankan semua jenis kueri yang memerlukan perhitungan dan kemudian menggunakan nilai itu di banyak tempat, misalnya dalam select
dan order by
, akan SQL Server menghitungnya dua kali untuk setiap baris atau akan di-cache? Selanjutnya, bagaimana cara kerjanya dengan Fungsi yang Ditentukan Pengguna?
Contoh:
SELECT CompanyId, Count(*)
FROM Sales
ORDER BY Count(*) desc
SELECT Geom.BufferWithTolerance(@radius, 0.01, 0).STEnvelope().STPointN(1).STX, Geom.BufferWithTolerance(@radius, 0.01, 0).STEnvelope().STPointN(1).STY
FROM Table
SELECT Id, udf.MyFunction(Id)
FROM Table
ORDER BY udf.MyFunction(Id)
Apakah ada cara untuk membuatnya lebih efisien atau SQL Server cukup pintar untuk menanganinya untuk saya?
sql-server
Jonas Stawski
sumber
sumber
SELECT RAND() FROM Sales order by RAND()
- ini hanya dievaluasi sekali karena keduanya non deterministik dan run time constant.Jawaban:
Pengoptimal kueri SQL Server dapat menggabungkan nilai terhitung berulang menjadi satu operator Compute Scalar. Apakah itu akan melakukannya atau tidak tergantung pada biaya rencana kueri dan properti dari nilai yang dihitung. Seperti yang diharapkan, itu tidak akan melakukan ini untuk nilai yang dihitung yang tidak deterministik, yang beberapa pengecualian seperti
RAND()
. Ini juga tidak akan melakukan ini untuk fungsi yang ditentukan pengguna.Saya akan mulai dengan contoh fungsi yang ditentukan pengguna. Berikut adalah contoh yang sangat baik dari fungsi yang ditentukan pengguna:
Saya juga ingin membuat tabel dan menempatkan 100 baris ke dalamnya:
The
dbo.NULL_FUNCTION
fungsi determistic. Berapa kali akan dieksekusi untuk permintaan berikut?Berdasarkan rencana permintaan, ini akan dieksekusi satu kali untuk setiap baris, atau 100 kali:
SQL Server 2016 memperkenalkan sys.dm_exec_function_stats DMV. Kami dapat mengambil snapshot dari DMV itu untuk melihat berapa kali UDF dieksekusi oleh sebuah query.
Hasilnya adalah 100, jadi fungsinya dijalankan 100 kali.
Mari kita coba pertanyaan sederhana lainnya:
Rencana kueri menyarankan bahwa fungsi tersebut akan dieksekusi 200 kali:
Hasil
sys.dm_exec_function_stats
menyarankan bahwa fungsi dijalankan 200 kali.Perhatikan bahwa Anda tidak selalu dapat menggunakan paket kueri untuk mengetahui berapa kali skalar komputasi dijalankan. Kutipan berikut berasal dari " Hitung Kerangka, Ekspresi, dan Kinerja Rencana Eksekusi ":
Mari kita coba contoh lain. Untuk pertanyaan berikut, saya berharap UDF dihitung satu kali:
Rencana kueri menyarankan bahwa itu akan dihitung satu kali:
Namun, DMV mengungkapkan kebenaran. Komputasi skalar ditangguhkan sampai dibutuhkan, yang ada di operator bergabung. Itu dievaluasi 100 kali.
Anda juga bertanya apa yang dapat Anda lakukan untuk mendorong pengoptimal agar tidak menghitung ulang ekspresi yang sama beberapa kali. Hal terbaik yang dapat Anda lakukan adalah menghindari menggunakan skalar UDF dalam kode Anda. Mereka memiliki sejumlah masalah kinerja di luar pertanyaan ini, termasuk menggembungkan hibah memori, memaksa seluruh permintaan untuk dijalankan
MAXDOP 1
, perkiraan kardinalitas buruk, dan mengarah pada pemanfaatan CPU tambahan. Jika Anda perlu menggunakan UDF dan nilai UDF itu adalah konstan, Anda dapat menghitungnya di luar kueri dan memasukkannya ke dalam variabel lokal.Untuk kueri tanpa UDF, Anda dapat mencoba menghindari menulis ekspresi yang mengembalikan hasil yang sama tetapi tidak diketik dengan cara yang persis sama. Untuk contoh berikut ini, saya menggunakan basis data AdventureworksDW2016CTP3 yang tersedia untuk umum, tetapi sebenarnya semua basis data akan melakukannya. Berapa kali akan
COUNT(*)
dihitung untuk permintaan ini?Untuk kueri ini, kita bisa mencari tahu ini dengan melihat operator Hash Match (agregat).
The
COUNT(*)
dihitung sekali untuk setiap nilai unik dariOrderDateKey
. TermasukORDER BY
klausa tidak menyebabkannya dihitung dua kali. Anda dapat melihat rencana eksekusi di sini .Sekarang, pertimbangkan permintaan yang akan mengembalikan hasil yang sama persis tetapi ditulis dengan cara yang berbeda:
Pengoptimal kueri tidak cukup pintar untuk menggabungkannya, jadi pekerjaan tambahan akan dilakukan:
sumber