Fungsi bernilai tabel multi-pernyataan mengembalikan hasilnya dalam variabel tabel.
Apakah hasil ini pernah digunakan kembali, atau apakah fungsi selalu sepenuhnya dievaluasi setiap kali dipanggil?
Fungsi bernilai tabel multi-pernyataan mengembalikan hasilnya dalam variabel tabel.
Apakah hasil ini pernah digunakan kembali, atau apakah fungsi selalu sepenuhnya dievaluasi setiap kali dipanggil?
Jawaban:
Hasil dari fungsi bernilai tabel multi-pernyataan (msTVF) tidak pernah cache atau digunakan kembali di seluruh pernyataan (atau koneksi), tetapi ada beberapa cara bahwa hasil msTVF dapat digunakan kembali dalam pernyataan yang sama. Sejauh itu, sebuah msTVF tidak perlu dihuni kembali setiap kali disebut.
MsTVF (sengaja tidak efisien) ini mengembalikan rentang integer yang ditentukan, dengan cap waktu di setiap baris:
IF OBJECT_ID(N'dbo.IntegerRange', 'TF') IS NOT NULL
DROP FUNCTION dbo.IntegerRange;
GO
CREATE FUNCTION dbo.IntegerRange (@From integer, @To integer)
RETURNS @T table
(
n integer PRIMARY KEY,
ts datetime DEFAULT CURRENT_TIMESTAMP
)
WITH SCHEMABINDING
AS
BEGIN
WHILE @From <= @To
BEGIN
INSERT @T (n)
VALUES (@From);
SET @From = @From + 1;
END;
RETURN;
END;
Jika semua parameter untuk panggilan fungsi adalah konstanta (atau konstanta runtime), rencana eksekusi akan mengisi hasil variabel tabel sekali. Sisa dari rencana dapat mengakses variabel tabel berkali-kali. Sifat statis dari variabel tabel dapat dikenali dari rencana eksekusi. Sebagai contoh:
SELECT
IR.n,
IR.ts
FROM dbo.IntegerRange(1, 5) AS IR
ORDER BY
IR.n;
Mengembalikan hasil yang mirip dengan:
Rencana pelaksanaannya adalah:
Operator Sequence pertama-tama memanggil operator Table Valued Function, yang mengisi variabel tabel (perhatikan operator ini tidak mengembalikan baris). Selanjutnya, Sequence memanggil input kedua, yang mengembalikan konten variabel tabel (menggunakan Clustered Index Scan dalam kasus ini).
Hadiah bahwa rencana tersebut menggunakan hasil tabel variabel 'statis' adalah operator Fungsi Bernilai Tabel di bawah Urutan - variabel tabel perlu diisi penuh satu kali sebelum sisa rencana dapat berjalan.
Untuk menunjukkan hasil variabel tabel yang diakses lebih dari sekali, kami akan menggunakan tabel kedua dengan baris bernomor 1 hingga 5:
IF OBJECT_ID(N'dbo.T', 'U') IS NOT NULL
DROP TABLE dbo.T;
CREATE TABLE dbo.T (i integer NOT NULL);
INSERT dbo.T (i)
VALUES (1), (2), (3), (4), (5);
Dan permintaan baru yang bergabung tabel ini untuk fungsi kita (ini bisa sama-sama ditulis sebagai APPLY
):
SELECT T.i,
IR.n,
IR.ts
FROM dbo.T AS T
JOIN dbo.IntegerRange(1, 5) AS IR
ON IR.n = T.i;
Hasilnya adalah:
Rencana pelaksanaan:
Seperti sebelumnya, Sequence mempopulasikan hasil tabel variabel msTVF terlebih dahulu. Selanjutnya, loop bersarang digunakan untuk bergabung dengan setiap baris dari tabelT
ke baris dari hasil msTVF. Karena definisi fungsi menyertakan indeks bermanfaat pada variabel tabel, pencarian indeks dapat digunakan.
Poin kuncinya adalah ketika parameter ke msTVF adalah konstanta (termasuk variabel & parameter) atau diperlakukan sebagai konstanta runtime untuk pernyataan oleh mesin eksekusi, paket tersebut akan menampilkan dua operator terpisah untuk hasil variabel tabel msTVF: satu untuk mengisi meja; lain untuk mengakses hasil, mungkin mengakses tabel beberapa kali, dan mungkin menggunakan indeks yang dinyatakan dalam definisi fungsi.
Untuk menyorot perbedaan ketika parameter berkorelasi (referensi luar) atau parameter fungsi tidak konstan digunakan, kami akan mengubah isi tabel T
sehingga fungsi memiliki lebih banyak pekerjaan yang harus dilakukan:
TRUNCATE TABLE dbo.T;
INSERT dbo.T (i)
VALUES (50001), (50002), (50003), (50004), (50005);
Kueri yang dimodifikasi berikut ini sekarang menggunakan referensi luar ke tabel T
di salah satu parameter fungsi:
SELECT T.i,
IR.n,
IR.ts
FROM dbo.T AS T
CROSS APPLY dbo.IntegerRange(1, T.i) AS IR
WHERE IR.n = T.i;
Permintaan ini membutuhkan waktu sekitar 8 detik untuk mengembalikan hasil seperti:
Perhatikan perbedaan waktu antara baris dalam kolom ts
. The WHERE
klausul membatasi hasil akhir untuk output bijaksana berukuran, tapi fungsi tidak efisien masih membutuhkan waktu untuk mengisi variabel meja dengan baris 50.000-aneh (tergantung pada nilai berkorelasi dari i
dari tabel T
).
Rencana pelaksanaannya adalah:
Perhatikan kurangnya operator Sequence. Sekarang, ada operator fungsi Table Valued Function tunggal yang mengisi variabel tabel dan mengembalikan barisnya pada setiap iterasi dari loop bersarang bergabung.
Agar lebih jelas: dengan hanya 5 baris pada tabel T, operator Function Table Valued berjalan 5 kali. Ini menghasilkan 50.001 baris pada iterasi pertama, 50.002 pada yang kedua ... dan seterusnya. Variabel tabel 'dibuang' (terpotong) di antara iterasi, sehingga masing-masing dari lima panggilan adalah populasi penuh. Inilah sebabnya mengapa sangat lambat, dan setiap baris membutuhkan waktu yang hampir bersamaan untuk muncul di hasilnya.
Catatan samping:
Tentu saja, skenario di atas sengaja dibuat untuk menunjukkan betapa buruknya kinerja ketika msTVF mengisi banyak baris pada setiap iterasi.
Sebuah masuk akal pelaksanaan kode di atas akan mengatur kedua parameter msTVF untuk i
, dan menghapus berlebihan WHERE
klausa. Variabel tabel masih akan dipotong dan diisi ulang pada setiap iterasi, tetapi hanya dengan satu baris setiap kali.
Kita juga bisa mengambil nilai minimum dan maksimum i
dari T
dan menyimpannya dalam variabel di langkah sebelumnya. Memanggil fungsi dengan variabel alih-alih parameter berkorelasi akan memungkinkan pola variabel tabel 'statis' digunakan seperti yang disebutkan sebelumnya.
Kembali ke menjawab pertanyaan asli sekali lagi, di mana pola statis Sequence tidak dapat digunakan, SQL Server dapat menghindari memotong dan mengisi kembali variabel tabel msTVF jika tidak ada parameter berkorelasi telah berubah sejak iterasi sebelumnya dari loop bersarang bergabung.
Untuk menunjukkan ini, kami akan mengganti konten T
dengan lima nilai yang identik i
:
TRUNCATE TABLE dbo.T;
INSERT dbo.T (i)
VALUES (50005), (50005), (50005), (50005), (50005);
Kueri dengan parameter berkorelasi lagi:
SELECT T.i,
IR.n,
IR.ts
FROM dbo.T AS T
CROSS APPLY dbo.IntegerRange(1, T.i) AS IR
WHERE IR.n = T.i;
Kali ini hasilnya muncul sekitar 1,5 detik :
Perhatikan cap waktu yang identik di setiap baris. Hasil cache dalam variabel tabel digunakan kembali untuk iterasi berikutnya di mana nilai berkorelasi i
tidak berubah. Menggunakan kembali hasilnya jauh lebih cepat daripada memasukkan 50.005 baris setiap kali.
Rencana eksekusi terlihat sangat mirip dengan sebelumnya:
Perbedaan utama adalah dalam properti Rebind Aktual dan Rewind Aktual dari operator Fungsi Table Valued:
Ketika parameter berkorelasi tidak berubah, SQL Server dapat memutar ulang (mundur) hasil saat ini dalam variabel tabel. Ketika korelasi berubah, SQL Server harus memotong dan mengisi kembali variabel tabel (rebind). Satu rebind terjadi pada iterasi pertama; keempat iterasi berikutnya semuanya mundur karena nilai T.i
tidak berubah.