Sonuç olarak, SQL Server'ı bir sorguda yalnızca bir kez skaler UDF'yi değerlendirmeye zorlamak mümkün değildir. Bununla birlikte, onu teşvik etmek için atılabilecek bazı adımlar vardır. Test ile SQL Server'ın mevcut sürümü ile çalışan bir şey elde edebileceğinizi düşünüyorum, ancak gelecekteki değişikliklerin kodunuzu tekrar ziyaret etmenizi gerektirmesi mümkündür.
Kodu düzenlemek mümkünse iyi bir ilk şey, fonksiyonu mümkünse deterministik hale getirmektir. Paul White burada işlevin SCHEMABINDING
seçenekle oluşturulması gerektiğini ve işlev kodunun kendisinin deterministik olması gerektiğini belirtir .
Aşağıdaki değişikliği yaptıktan sonra:
CREATE OR ALTER FUNCTION dbo.EXPENSIVE_UDF () RETURNS INT
WITH SCHEMABINDING
AS
BEGIN
DECLARE @tbl TABLE (VAL VARCHAR(5));
-- make the function expensive to call
INSERT INTO @tbl
SELECT [VALUE]
FROM STRING_SPLIT(REPLICATE(CAST('Z ' AS VARCHAR(MAX)), 20000), ' ');
RETURN 1;
END;
Sorudan gelen sorgu 64 ms'de yürütülür:
SELECT x1.ID
FROM dbo.X_100_INTEGERS x1
WHERE x1.ID >= dbo.EXPENSIVE_UDF();
Sorgu planında artık filtre operatörü yok:
SQL Server 2016'da piyasaya sürülen yeni sys.dm_exec_function_stats DMV'yi kullanabildiğimizden emin olmak için :
SELECT execution_count
FROM sys.dm_exec_function_stats
WHERE object_id = OBJECT_ID('EXPENSIVE_UDF', 'FN');
İşleve ALTER
karşı bir işlev verilmesi execution_count
, o nesnenin sıfırlanmasına neden olur. Yukarıdaki sorgu 1 ile döner; bu fonksiyonun sadece bir kez yürütüldüğü anlamına gelir.
Fonksiyonun deterministik olması, herhangi bir sorgu için sadece bir kez değerlendirileceği anlamına gelmez. Aslında, bazı sorgular için ekleme SCHEMABINDING
performansı düşürebilir. Aşağıdaki sorguyu düşünün:
WITH cte (UDF_VALUE) AS
(
SELECT DISTINCT dbo.EXPENSIVE_UDF() UDF_VALUE
)
SELECT ID
FROM dbo.X_100_INTEGERS
INNER JOIN cte ON ID >= cte.UDF_VALUE;
DISTINCT
Bir Filtre operatöründen kurtulmak için gereksiz eklenmiştir. Plan umut verici görünüyor:
Buna dayanarak, UDF'nin bir kez değerlendirilmesini ve iç içe döngü birleşiminde dış tablo olarak kullanılmasını bekleyebilirsiniz. Ancak, sorgu benim makinede çalıştırmak için 6446 ms sürer. sys.dm_exec_function_stats
Fonksiyona göre 100 kez idam edildi. Bu nasıl mümkün olabilir? " Hesaplamalı Skalerler, İfadeler ve Uygulama Planı Performansı " nda Paul White, Hesaplamalı Skaler operatörünün ertelenebileceğine dikkat çekiyor:
Çoğu zaman, bir Hesaplama Skaleri basitçe bir ifadeyi tanımlar; gerçek hesaplama, yürütme planında daha sonraki bir şeyin sonuca ihtiyacı olana kadar ertelenir.
Bu sorgu için UDF çağrısı gerekene kadar ertelenmiş gibi görünüyor, bu noktada 100 kez değerlendirildi.
İlginç bir şekilde, CTE örneği SCHEMABINDING
orijinal sorudaki gibi UDF ile tanımlanmadığında makinemde 71 ms'de yürütülür . İşlev, sorgu çalıştırıldığında yalnızca bir kez yürütülür. İşte bunun için sorgu planı:
Compute Skaler'ın neden ertelenmediği açık değil. Bunun nedeni, işlevin belirsizliğinin, sorgu eniyileyicisinin yapabileceği işleçlerin yeniden düzenlenmesini sınırlaması olabilir.
Alternatif bir yaklaşım, CTE'ye küçük bir tablo eklemek ve bu tablodaki tek satırı sorgulamaktır. Herhangi bir küçük tablo yapacak, ancak aşağıdakileri kullanalım:
CREATE TABLE dbo.X_ONE_ROW_TABLE (ID INT NOT NULL);
INSERT INTO dbo.X_ONE_ROW_TABLE VALUES (1);
Ardından sorgu:
WITH cte (UDF_VALUE) AS
(
SELECT DISTINCT dbo.EXPENSIVE_UDF() UDF_VALUE
FROM dbo.X_ONE_ROW_TABLE
)
SELECT ID
FROM dbo.X_100_INTEGERS
INNER JOIN cte ON ID >= cte.UDF_VALUE;
Eklenmesi dbo.X_ONE_ROW_TABLE
optimize edici için belirsizlik ekler. Tablonun sıfır satırı varsa, CTE 0 satır döndürür. Her durumda, optimize edici, UDF belirleyici değilse CTE'nin bir satır döndüreceğini garanti edemez, bu nedenle UDF'nin birleştirmeden önce değerlendirileceği düşünülmektedir. Optimize edicinin tarama dbo.X_ONE_ROW_TABLE
, bir satır döndürülen (bu işlevin değerlendirilmesini gerektiren) maksimum değeri elde etmek için bir akış toplaması kullanın dbo.X_100_INTEGERS
ve ana sorguda iç içe döngü katılmak için dış tablo olarak kullanmak için beklenir . Bu gibi görünen ne olur :
Sorgu makinemde yaklaşık 110 ms'de yürütülür ve UDF'ye göre yalnızca bir kez değerlendirilir sys.dm_exec_function_stats
. Sorgu iyileştiricinin UDF'yi yalnızca bir kez değerlendirmek zorunda kaldığını söylemek yanlış olur. Bununla birlikte, UDF ve hesaplama skaler maliyetleme ile ilgili sınırlamalarda bile daha düşük maliyetli bir sorguya yol açabilecek bir optimize edici yeniden yazma hayal etmek zordur.
Özetle, deterministik fonksiyonlar için ( SCHEMABINDING
seçeneği içermelidir ) sorguyu mümkün olduğunca basit bir şekilde yazmayı deneyin. SQL Server 2016 veya sonraki bir sürümdeyse, işlevin yalnızca bir kez kullanıldığını doğrulayın sys.dm_exec_function_stats
. İcra planları bu konuda yanıltıcı olabilir.
SCHEMABINDING
Seçenek dışında herhangi bir şey dahil olmak üzere SQL Server tarafından dikkate alınmayan işlevlerin belirleyici olması için , bir yaklaşım UDF'yi özenle hazırlanmış bir CTE veya türetilmiş tabloya koymaktır. Bu biraz bakım gerektirir, ancak aynı CTE hem deterministik hem de belirsiz olmayan işlevler için çalışabilir.