Inlining UDF skalar
Berlaku untuk: SQL Server 2019 (15.x) Azure SQL Database Azure SQL Managed Instance
Artikel ini memperkenalkan inlining UDF skalar, fitur di bawah pemrosesan kueri cerdas dalam rangkaian fitur database SQL. Fitur ini meningkatkan performa kueri yang memanggil UDF skalar di SQL Server 2019 (15.x) dan versi yang lebih baru.
Fungsi yang ditentukan pengguna skalar T-SQL
Fungsi yang Ditentukan Pengguna (UDF) yang diimplementasikan dalam Transact-SQL dan mengembalikan satu nilai data disebut sebagai Fungsi yang Ditentukan Pengguna Skalar T-SQL. T-SQL UDF adalah cara elegan untuk mencapai penggunaan kembali kode dan modularitas di seluruh kueri Transact-SQL. Beberapa komputasi (seperti aturan bisnis yang kompleks) lebih mudah diekspresikan dalam bentuk UDF imperatif. UDF membantu dalam membangun logika yang kompleks tanpa memerlukan keahlian dalam menulis kueri SQL yang kompleks. Untuk informasi selengkapnya tentang UDF, lihat Membuat fungsi yang ditentukan pengguna (Mesin Database).
Performa UDF skalar
UDF skalar biasanya berkinerja buruk karena alasan berikut:
Pemanggilan berulang. UDF dipanggil dengan cara berulang, sekali per tuple yang memenuhi syarat. Ini menimbulkan biaya tambahan pengalihan konteks berulang karena pemanggilan fungsi. Terutama, UDF yang menjalankan kueri Transact-SQL dalam definisinya sangat terpengaruh.
Kurangnya biaya. Selama pengoptimalan, hanya operator relasional yang dikenakan biaya, sementara operator skalar tidak. Sebelum pengenalan UDF skalar, operator skalar lainnya umumnya murah dan tidak memerlukan biaya. Biaya CPU kecil yang ditambahkan untuk operasi skalar sudah cukup. Ada skenario di mana biaya aktual signifikan, namun masih tetap kurang terwajibkan.
Ditafsirkan eksekusi. UDF dievaluasi sebagai batch pernyataan, dieksekusi pernyataan demi pernyataan. Setiap pernyataan itu sendiri dikompilasi, dan rencana yang dikompilasi di-cache. Meskipun strategi penembolokan ini menghemat waktu karena menghindari kompilasi ulang, setiap pernyataan dijalankan dalam isolasi. Tidak ada pengoptimalan lintas pernyataan yang dilakukan.
Eksekusi serial. SQL Server tidak mengizinkan paralelisme intra-query dalam kueri yang memanggil UDF.
Inlining otomatis UDF skalar
Tujuan dari fitur inlining UDF skalar adalah untuk meningkatkan performa kueri yang memanggil UDF skalar T-SQL, di mana eksekusi UDF adalah hambatan utama.
Dengan fitur baru ini, UDF skalar secara otomatis diubah menjadi ekspresi skalar atau subkueri skalar yang digantikan dalam kueri panggilan sebagai pengganti operator UDF. Ekspresi dan subkueri ini kemudian dioptimalkan. Akibatnya, rencana kueri tidak lagi memiliki operator fungsi yang ditentukan pengguna, tetapi efeknya diamati dalam rencana, seperti tampilan atau fungsi bernilai tabel sebaris (TVF).
Contoh
Contoh di bagian ini menggunakan database tolok ukur TPC-H. Untuk informasi selengkapnya, lihat Beranda TPC-H.
J. Pernyataan tunggal skalar UDF
Pertimbangkan kueri berikut.
SELECT L_SHIPDATE,
O_SHIPPRIORITY,
SUM(L_EXTENDEDPRICE * (1 - L_DISCOUNT))
FROM LINEITEM
INNER JOIN ORDERS
ON O_ORDERKEY = L_ORDERKEY
GROUP BY L_SHIPDATE, O_SHIPPRIORITY
ORDER BY L_SHIPDATE;
Kueri ini menghitung jumlah harga diskon untuk item baris dan menyajikan hasil yang dikelompokkan berdasarkan tanggal pengiriman dan prioritas pengiriman. Ekspresi L_EXTENDEDPRICE *(1 - L_DISCOUNT)
adalah rumus untuk harga diskon untuk item baris tertentu. Rumus tersebut dapat diekstrak ke dalam fungsi untuk kepentingan modularitas dan penggunaan kembali.
CREATE FUNCTION dbo.discount_price
(
@price DECIMAL (12, 2),
@discount DECIMAL (12, 2)
)
RETURNS DECIMAL (12, 2)
AS
BEGIN
RETURN @price * (1 - @discount);
END
Sekarang kueri dapat dimodifikasi untuk memanggil UDF ini.
SELECT L_SHIPDATE,
O_SHIPPRIORITY,
SUM(dbo.discount_price(L_EXTENDEDPRICE, L_DISCOUNT))
FROM LINEITEM
INNER JOIN ORDERS
ON O_ORDERKEY = L_ORDERKEY
GROUP BY L_SHIPDATE, O_SHIPPRIORITY
ORDER BY L_SHIPDATE;
Kueri dengan UDF berkinerja buruk, karena alasan yang diuraikan sebelumnya. Dengan inlining UDF skalar, ekspresi skalar dalam isi UDF digantikan langsung dalam kueri. Hasil menjalankan kueri ini diperlihatkan dalam tabel berikut ini:
Kueri: | Kueri tanpa UDF | Kueri dengan UDF (tanpa inlining) | Kueri dengan inlining UDF skalar |
---|---|---|---|
Waktu eksekusi: | 1,6 detik | 29 menit 11 detik | 1,6 detik |
Angka-angka ini didasarkan pada database CCI 10 GB (menggunakan skema TPC-H), berjalan pada mesin dengan prosesor ganda (12 inti), RAM 96 GB, didukung oleh SSD. Angka-angka tersebut termasuk kompilasi dan waktu eksekusi dengan cache prosedur dingin dan kumpulan buffer. Konfigurasi default digunakan, dan tidak ada indeks lain yang dibuat.
B. UDF skalar multi-pernyataan
UDF skalar yang diimplementasikan menggunakan beberapa pernyataan T-SQL seperti penetapan variabel dan pencabangan bersyarat juga dapat di-inlin. Pertimbangkan UDF skalar berikut yang, mengingat kunci pelanggan, menentukan kategori layanan untuk pelanggan tersebut. Ini tiba di kategori dengan terlebih dahulu menghitung harga total semua pesanan yang dilakukan oleh pelanggan menggunakan kueri SQL. Kemudian, menggunakan IF (...) ELSE
logika untuk memutuskan kategori berdasarkan harga total.
CREATE OR ALTER FUNCTION dbo.customer_category (@ckey INT)
RETURNS CHAR (10)
AS
BEGIN
DECLARE @total_price AS DECIMAL (18, 2);
DECLARE @category AS CHAR (10);
SELECT @total_price = SUM(O_TOTALPRICE)
FROM ORDERS
WHERE O_CUSTKEY = @ckey;
IF @total_price < 500000
SET @category = 'REGULAR';
ELSE
IF @total_price < 1000000
SET @category = 'GOLD';
ELSE
SET @category = 'PLATINUM';
RETURN @category;
END
Sekarang, pertimbangkan kueri yang memanggil UDF ini.
SELECT C_NAME,
dbo.customer_category(C_CUSTKEY)
FROM CUSTOMER;
Rencana eksekusi untuk kueri ini di SQL Server 2017 (14.x) (tingkat kompatibilitas 140 dan yang lebih lama) adalah sebagai berikut:
Seperti yang ditunjukkan oleh rencana, SQL Server mengadopsi strategi sederhana di sini: untuk setiap tuple dalam CUSTOMER
tabel, panggil UDF dan keluarkan hasilnya. Strategi ini naif dan tidak efisien. Dengan inlining, UDF tersebut diubah menjadi subkueri skalar yang setara, yang digantikan dalam kueri panggilan sebagai pengganti UDF.
Untuk kueri yang sama, rencana dengan UDF bergaris terlihat sebagai berikut.
Seperti disebutkan sebelumnya, rencana kueri tidak lagi memiliki operator fungsi yang ditentukan pengguna, tetapi efeknya sekarang dapat diamati dalam paket, seperti tampilan atau TVF sebaris. Berikut adalah beberapa pengamatan utama dari rencana sebelumnya:
SQL Server menyimpulkan gabungan implisit antara
CUSTOMER
danORDERS
membuatnya eksplisit melalui operator gabungan.SQL Server juga menyimpulkan implisit
GROUP BY O_CUSTKEY on ORDERS
dan menggunakan IndexSpool + StreamAggregate untuk mengimplementasikannya.SQL Server sekarang menggunakan paralelisme di semua operator.
Bergantung pada kompleksitas logika di UDF, rencana kueri yang dihasilkan mungkin juga menjadi lebih besar dan lebih kompleks. Seperti yang kita lihat, operasi di dalam UDF sekarang tidak lagi buram, sehingga pengoptimal kueri dapat menelan biaya dan mengoptimalkan operasi tersebut. Selain itu, karena UDF tidak lagi dalam rencana, pemanggilan UDF berulang digantikan oleh rencana yang sepenuhnya menghindari overhead panggilan fungsi.
Persyaratan UDF skalar yang tidak sebaris
T-SQL UDF skalar dapat di-inlin jika definisi fungsi menggunakan konstruksi yang diizinkan, dan fungsi digunakan dalam konteks yang memungkinkan inlining:
Semua kondisi definisi UDF berikut harus benar:
- UDF ditulis menggunakan konstruksi berikut:
DECLARE
,SET
: Deklarasi variabel dan penugasan.SELECT
: Kueri SQL dengan penetapan variabel tunggal/beberapa 1.IF
/ELSE
: Bercabang dengan tingkat bersarang sewenang-wenang.RETURN
: Pernyataan pengembalian tunggal atau beberapa. Dimulai dengan SQL Server 2019 (15.x) CU5, UDF hanya dapat berisi satu pernyataan RETURN yang akan dipertimbangkan untuk inlining 6.UDF
: Fungsi berlapis/rekursif memanggil 2.- Lainnya: Operasi relasional seperti
EXISTS
,IS NULL
.
- UDF tidak memanggil fungsi intrinsik apa pun yang bergantung pada waktu (seperti
GETDATE()
) atau memiliki efek samping 3 (sepertiNEWSEQUENTIALID()
). - UDF menggunakan klausul
EXECUTE AS CALLER
(perilaku default jikaEXECUTE AS
klausul tidak ditentukan). - UDF tidak mereferensikan variabel tabel atau parameter bernilai tabel.
- UDF tidak dikompilasi secara asli (interop didukung).
- UDF tidak mereferensikan jenis yang ditentukan pengguna.
- Tidak ada tanda tangan yang ditambahkan ke UDF 9.
- UDF bukan fungsi partisi.
- UDF tidak berisi referensi ke Common Table Expressions (CTEs).
- UDF tidak berisi referensi ke fungsi intrinsik yang mungkin mengubah hasil ketika inlined (seperti
@@ROWCOUNT
) 4. - UDF tidak berisi fungsi agregat yang diteruskan sebagai parameter ke skalar UDF 4.
- UDF tidak mereferensikan tampilan bawaan (seperti
OBJECT_ID
) 4. - UDF tidak mereferensikan metode XML 5.
- UDF tidak berisi SELECT dengan
ORDER BY
tanpaTOP 1
klausul 5. - UDF tidak berisi kueri SELECT yang melakukan penugasan dengan
ORDER BY
klausa (sepertiSELECT @x = @x + 1 FROM table1 ORDER BY col1
) 5. - UDF tidak berisi beberapa pernyataan RETURN 6.
- UDF tidak mereferensikan
STRING_AGG
fungsi 6. - UDF tidak mereferensikan tabel jarak jauh 7.
- UDF tidak mereferensikan kolom terenkripsi 8.
- UDF tidak berisi referensi ke
WITH XMLNAMESPACES
8. - Jika definisi UDF berjalan ke ribuan baris kode, SQL Server mungkin memilih untuk tidak meng-sebariskannya.
1 SELECT
dengan akumulasi/agregasi variabel tidak didukung untuk inlining (seperti SELECT @val += col1 FROM table1
).
2 UDF rekursif hanya disebarkan ke kedalaman tertentu.
3 Fungsi intrinsik yang hasilnya bergantung pada waktu sistem saat ini bergantung pada waktu. Fungsi intrinsik yang mungkin memperbarui beberapa status global internal adalah contoh fungsi dengan efek samping. Fungsi tersebut mengembalikan hasil yang berbeda setiap kali dipanggil, berdasarkan status internal.
4 Pembatasan ditambahkan di SQL Server 2019 (15.x) CU 2
5 Pembatasan ditambahkan di SQL Server 2019 (15.x) CU 4
6 Pembatasan ditambahkan di SQL Server 2019 (15.x) CU 5
7 Pembatasan ditambahkan di SQL Server 2019 (15.x) CU 6
8 Pembatasan ditambahkan di SQL Server 2019 (15.x) CU 11
9 Karena tanda tangan dapat ditambahkan dan dihilangkan setelah UDF dibuat, keputusan apakah akan sebaris dilakukan ketika kueri yang merujuk UDF skalar dikompilasi. Misalnya, fungsi sistem biasanya ditandatangani dengan sertifikat. Anda dapat menggunakan sys.crypt_properties untuk menemukan objek mana yang ditandatangani.
Semua persyaratan konteks eksekusi berikut harus benar:
- UDF tidak digunakan dalam
ORDER BY
klausa. - Kueri yang memanggil UDF skalar tidak mereferensikan panggilan UDF skalar dalam klausanya
GROUP BY
. - Kueri yang memanggil UDF skalar dalam daftar pemilihannya dengan
DISTINCT
klausa tidak memilikiORDER BY
klausa. - UDF tidak dipanggil dari pernyataan RETURN 1.
- Kueri yang memanggil UDF tidak memiliki ekspresi tabel umum (CTA) 3.
- Kueri panggilan UDF tidak menggunakan
GROUPING SETS
, ,CUBE
atauROLLUP
2. - Kueri panggilan UDF tidak berisi variabel yang digunakan sebagai parameter UDF untuk penugasan (misalnya,
SELECT @y = 2
,@x = UDF(@y)
) 2. - UDF tidak digunakan dalam kolom komputasi atau definisi batasan pemeriksaan.
1 Pembatasan ditambahkan di SQL Server 2019 (15.x) CU 5
2 Pembatasan ditambahkan di SQL Server 2019 (15.x) CU 6
3 Pembatasan ditambahkan di SQL Server 2019 (15.x) CU 11
Untuk informasi tentang perbaikan inlining UDF skalar T-SQL terbaru dan perubahan pada skenario kelayakan yang menginlining, lihat artikel Pangkalan Pengetahuan: MEMPERBAIKI: masalah inlining UDF skalar di SQL Server 2019.
Periksa apakah UDF dapat di-inlin
Untuk setiap UDF skalar T-SQL, tampilan katalog sys.sql_modules menyertakan properti yang disebut is_inlineable
, yang menunjukkan apakah UDF tidak sebaris.
Properti is_inlineable
berasal dari konstruksi yang ditemukan di dalam definisi UDF. Ini tidak memeriksa apakah UDF sebenarnya sebaris pada waktu kompilasi. Untuk informasi selengkapnya, lihat kondisi untuk inlining.
Nilai 1
menunjukkan bahwa UDF tidak sebaris, dan 0
menunjukkan sebaliknya. Properti ini juga memiliki nilai 1
untuk semua TVF sebaris. Untuk semua modul lainnya, nilainya adalah 0
.
Jika UDF skalar tidak sebaris, itu tidak menyiratkan bahwa itu selalu sebaris. SQL Server memutuskan (berdasarkan per kueri, per-UDF) apakah akan menginline UDF. Lihat daftar persyaratan sebelumnya di artikel ini.
SELECT *
FROM sys.crypt_properties AS cp
INNER JOIN sys.objects AS o
ON cp.major_id = o.object_id;
Periksa apakah inlining telah terjadi
Jika semua prasyarat terpenuhi dan SQL Server memutuskan untuk melakukan inlining, itu mengubah UDF menjadi ekspresi relasional. Dari rencana kueri, Anda bisa mencari tahu apakah terjadi inlining:
- XML paket tidak memiliki
<UserDefinedFunction>
simpul XML untuk UDF yang berhasil di-inlin. - Peristiwa Tertentu yang Diperluas dipancarkan.
Mengaktifkan inlining UDF skalar
Anda dapat membuat beban kerja secara otomatis memenuhi syarat untuk inlining UDF skalar dengan mengaktifkan tingkat kompatibilitas 150 untuk database. Anda dapat mengatur ini menggunakan Transact-SQL. Contohnya:
ALTER DATABASE [WideWorldImportersDW]
SET COMPATIBILITY_LEVEL = 150;
Terlepas dari langkah ini, tidak ada perubahan lain yang diperlukan untuk dilakukan pada UDF atau kueri untuk memanfaatkan fitur ini.
Menonaktifkan inlining UDF skalar tanpa mengubah tingkat kompatibilitas
Inlining UDF skalar dapat dinonaktifkan di database, pernyataan, atau cakupan UDF sambil tetap mempertahankan tingkat kompatibilitas database 150 dan yang lebih tinggi. Untuk menonaktifkan inlining UDF skalar pada cakupan database, jalankan pernyataan berikut dalam konteks database yang berlaku:
ALTER DATABASE SCOPED CONFIGURATION SET TSQL_SCALAR_UDF_INLINING = OFF;
Untuk mengaktifkan kembali inlining UDF skalar untuk database, jalankan pernyataan berikut dalam konteks database yang berlaku:
ALTER DATABASE SCOPED CONFIGURATION SET TSQL_SCALAR_UDF_INLINING = ON;
Ketika ON
, pengaturan ini muncul sebagai diaktifkan di sys.database_scoped_configurations.
Anda juga dapat menonaktifkan inlining UDF skalar untuk kueri tertentu dengan menunjuk DISABLE_TSQL_SCALAR_UDF_INLINING
sebagai USE HINT
petunjuk kueri.
USE HINT
Petunjuk kueri lebih diutamakan daripada pengaturan konfigurasi tercakup database atau tingkat kompatibilitas.
Contohnya:
SELECT L_SHIPDATE,
O_SHIPPRIORITY,
SUM(dbo.discount_price(L_EXTENDEDPRICE, L_DISCOUNT))
FROM LINEITEM
INNER JOIN ORDERS
ON O_ORDERKEY = L_ORDERKEY
GROUP BY L_SHIPDATE, O_SHIPPRIORITY
ORDER BY L_SHIPDATE
OPTION (USE HINT('DISABLE_TSQL_SCALAR_UDF_INLINING'));
Inlining UDF skalar juga dapat dinonaktifkan untuk UDF tertentu menggunakan klausa INLINE dalam CREATE FUNCTION
pernyataan atau ALTER FUNCTION
.
Contohnya:
CREATE OR ALTER FUNCTION dbo.discount_price
(
@price DECIMAL (12, 2),
@discount DECIMAL (12, 2)
)
RETURNS DECIMAL (12, 2)
WITH INLINE = OFF
AS
BEGIN
RETURN @price * (1 - @discount);
END
Setelah pernyataan sebelumnya dijalankan, UDF ini tidak pernah diinlin ke dalam kueri apa pun yang memanggilnya. Untuk mengaktifkan kembali inlining untuk UDF ini, jalankan pernyataan berikut:
CREATE OR ALTER FUNCTION dbo.discount_price
(
@price DECIMAL (12, 2),
@discount DECIMAL (12, 2)
)
RETURNS DECIMAL (12, 2)
WITH INLINE = ON
AS
BEGIN
RETURN @price * (1 - @discount);
END
Klausul INLINE
ini tidak wajib. INLINE
Jika klausul tidak ditentukan, klausa secara otomatis diatur ke ON
/OFF
berdasarkan apakah UDF dapat di-inlin. Jika INLINE = ON
ditentukan tetapi UDF ditemukan tidak memenuhi syarat untuk inlining, kesalahan akan muncul.
Keterangan
Seperti yang dijelaskan dalam artikel ini, skalar UDF inlining mengubah kueri dengan UDF skalar menjadi kueri dengan subkueri skalar yang setara. Karena transformasi ini, Anda mungkin melihat beberapa perbedaan perilaku dalam skenario berikut:
Inlining menghasilkan hash kueri yang berbeda untuk teks kueri yang sama.
Peringatan tertentu dalam pernyataan di dalam UDF (seperti dibagi dengan nol, dll.) yang mungkin sebelumnya disembunyikan, dapat muncul karena inlining.
Petunjuk gabungan tingkat kueri mungkin tidak valid lagi, karena inlining dapat memperkenalkan gabungan baru. Petunjuk gabungan lokal harus digunakan sebagai gantinya.
Tampilan yang mereferensikan UDF skalar sebaris tidak dapat diindeks. Jika Anda perlu membuat indeks pada tampilan tersebut, nonaktifkan inlining untuk UDF yang dirujuk.
Mungkin ada beberapa perbedaan dalam perilaku masking data Dinamis dengan UDF inlining.
Dalam situasi tertentu (tergantung pada logika di UDF), inlining mungkin lebih konservatif sehubungan dengan kolom output masking. Dalam skenario di mana kolom yang direferensikan dalam UDF bukan kolom output, kolom tersebut tidak ditutupi.
Jika UDF mereferensikan fungsi bawaan seperti
SCOPE_IDENTITY()
, ,@@ROWCOUNT
atau@@ERROR
, nilai yang dikembalikan oleh fungsi bawaan berubah dengan inlining. Perubahan perilaku ini karena inlining mengubah cakupan pernyataan di dalam UDF. Dimulai dengan SQL Server 2019 (15.x) CU2, inlining diblokir jika UDF mereferensikan fungsi intrinsik tertentu (misalnya@@ROWCOUNT
).Jika variabel ditetapkan dengan hasil UDF sebaris dan juga digunakan seperti
index_column_name
dalamFORCESEEK
petunjuk Kueri, itu menghasilkan kesalahan 8622, menunjukkan bahwa prosesor kueri tidak dapat menghasilkan rencana kueri karena petunjuk yang ditentukan dalam kueri.
Konten terkait
- Membuat fungsi yang ditentukan pengguna (Mesin Database)
- Pusat Performa untuk Mesin Database SQL Server dan Azure SQL Database
- Panduan arsitektur pemrosesan kueri
- Referensi operator showplan logis dan fisik
- Gabungan (SQL Server)
- Menunjukkan Pemrosesan Kueri Cerdas
- MEMPERBAIKI: skalar UDF inlining masalah di SQL Server 2019