Skaler UDF'yi bir sorguda yalnızca bir kez değerlendirilmeye nasıl zorlayabilirim?


12

Skaler UDF sonucuna karşı filtre gereken bir sorgu var. Sorgu tek bir deyim olarak gönderilmelidir (böylece UDF sonucunu yerel bir değişkene atayamam) ve bir TVF kullanamıyorum. Tüm planın seri olarak çalıştırılmasını zorlayan skaler UDF'lerin neden olduğu performans sorunlarının farkındayım, aşırı bellek hibeleri, kardinalite tahmini sorunları ve satır içi çizgi eksikliği. Bu soru için lütfen skaler UDF kullanmam gerektiğini varsayın.

UDF'nin kendisini aramak oldukça pahalıdır, ancak teoride sorgular optimize edici tarafından fonksiyonun sadece bir kez hesaplanması gerektiği şekilde mantıksal olarak uygulanabilir. Bu soru için çok basitleştirilmiş bir örnek oluşturdum. Aşağıdaki sorgu 6152 ms makinemde yürütmek için alır:

SELECT x1.ID
FROM dbo.X_100_INTEGERS x1
WHERE x1.ID >= dbo.EXPENSIVE_UDF();

Sorgu planındaki filtre operatörü , işlevin her satır için bir kez değerlendirildiğini gösterir:

sorgu planı 1

DDL ve veri hazırlama:

CREATE OR ALTER FUNCTION dbo.EXPENSIVE_UDF () RETURNS INT
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;

GO

DROP TABLE IF EXISTS dbo.X_100_INTEGERS;

CREATE TABLE dbo.X_100_INTEGERS (ID INT NOT NULL);

-- insert 100 integers from 1 - 100
WITH
    L0   AS(SELECT 1 AS c UNION ALL SELECT 1),
    L1   AS(SELECT 1 AS c FROM L0 AS A CROSS JOIN L0 AS B),
    L2   AS(SELECT 1 AS c FROM L1 AS A CROSS JOIN L1 AS B),
    L3   AS(SELECT 1 AS c FROM L2 AS A CROSS JOIN L2 AS B),
    L4   AS(SELECT 1 AS c FROM L3 AS A CROSS JOIN L3 AS B),
    L5   AS(SELECT 1 AS c FROM L4 AS A CROSS JOIN L4 AS B),
    Nums AS(SELECT ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) AS n FROM L5)
INSERT INTO dbo.X_100_INTEGERS WITH (TABLOCK)
SELECT n FROM Nums WHERE n <= 100;

Kodun yürütülmesi yaklaşık 18 saniye sürmesine rağmen, yukarıdaki örnek için bir db keman bağlantısı .

Bazı durumlarda, bir satıcı tarafından sağlandığından işlevin kodunu düzenleyemeyebilirim. Diğer durumlarda değişiklik yapabilirim. Skaler UDF'yi bir sorguda yalnızca bir kez değerlendirilmeye nasıl zorlayabilirim?

Yanıtlar:


17

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 SCHEMABINDINGseç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:

sorgu planı 1

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 ALTERkarşı 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 SCHEMABINDINGperformansı 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;

DISTINCTBir Filtre operatöründen kurtulmak için gereksiz eklenmiştir. Plan umut verici görünüyor:

sorgu planı 2

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_statsFonksiyona 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 SCHEMABINDINGorijinal 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ı:

sorgu planı 3

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_TABLEoptimize 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_INTEGERSve 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 planı 4

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 ( SCHEMABINDINGseç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.

SCHEMABINDINGSeç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.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.