Çoklu ifade TVF ve Inline TVF Performansı


18

Palindrome sorusundaki bazı cevapları ( cevabı sildiğimden sadece 10k + kullanıcı) karşılaştırarak kafa karıştırıcı sonuçlar alıyorum.

Standart bir işlevi çalıştırmaktan daha hızlı olacağını düşündüğüm çok ifadeli, şemaya bağlı bir TVF önerdim . Ayrıca, aşağıda göreceğiniz gibi, bu konuda yanlış olduğum halde, çoklu ifadeli TVF'nin "satır içi" olacağı izlenimindeydim. Bu soru, bu iki TVF stilinin performans farkı ile ilgilidir. İlk olarak, kodu görmeniz gerekir.

Çoklu ifade TVF'si:

IF OBJECT_ID('dbo.IsPalindrome') IS NOT NULL
DROP FUNCTION dbo.IsPalindrome;
GO
CREATE FUNCTION dbo.IsPalindrome
(
    @Word NVARCHAR(500)
) 
RETURNS @t TABLE
(
    IsPalindrome BIT NOT NULL
)
WITH SCHEMABINDING
AS
BEGIN
    DECLARE @IsPalindrome BIT;
    DECLARE @LeftChunk NVARCHAR(250);
    DECLARE @RightChunk NVARCHAR(250);
    DECLARE @StrLen INT;
    DECLARE @Pos INT;
    SET @RightChunk = '';
    SET @IsPalindrome = 0;
    SET @StrLen = LEN(@Word) / 2;
    IF @StrLen % 2 = 1 SET @StrLen = @StrLen - 1;
    SET @Pos = LEN(@Word);
    SET @LeftChunk = LEFT(@Word, @StrLen);
    WHILE @Pos > (LEN(@Word) - @StrLen)
    BEGIN
        SET @RightChunk = @RightChunk + SUBSTRING(@Word, @Pos, 1)
        SET @Pos = @Pos - 1;
    END
    IF @LeftChunk = @RightChunk SET @IsPalindrome = 1;
    INSERT INTO @t VALUES (@IsPalindrome);
    RETURN
END
GO

Satır içi TVF:

IF OBJECT_ID('dbo.InlineIsPalindrome') IS NOT NULL
DROP FUNCTION dbo.InlineIsPalindrome;
GO
CREATE FUNCTION dbo.InlineIsPalindrome
(
    @Word NVARCHAR(500)
)
RETURNS TABLE
WITH SCHEMABINDING
AS RETURN (
    WITH Nums AS
    (
      SELECT
        N = number
      FROM
        dbo.Numbers
    )
    SELECT
      IsPalindrome =
        CASE
          WHEN EXISTS
          (
            SELECT N
            FROM Nums
            WHERE N <= L / 2
              AND SUBSTRING(S, N, 1) <> SUBSTRING(S, 1 + L - N, 1)
          )
          THEN 0
          ELSE 1
        END
    FROM
      (SELECT LTRIM(RTRIM(@Word)), LEN(@Word)) AS v (S, L)
);
GO

NumbersYukarıdaki fonksiyonun tablo olarak tarif edilir:

CREATE TABLE dbo.Numbers
(
    Number INT NOT NULL 
);

Not: Sayılar tablosunda dizin veya birincil anahtar yoktur ve 1.000.000 satır içerir.

Test yatağı geçici tablosu:

IF OBJECT_ID('tempdb.dbo.#Words') IS NOT NULL
DROP TABLE #Words;
GO
CREATE TABLE #Words 
(
    Word VARCHAR(500) NOT NULL
);

INSERT INTO #Words(Word) 
SELECT o.name + REVERSE(w.name)
FROM sys.objects o
CROSS APPLY (
    SELECT o.name
    FROM sys.objects o
) w;

Test sistemimde yukarıdaki tabloya INSERT16.900 satır eklenir #Words.

İki varyasyonu test etmek için, I SET STATISTICS IO, TIME ON;ve aşağıdakileri kullanın:

SELECT w.Word
    , p.IsPalindrome
FROM #Words w
    CROSS APPLY dbo.IsPalindrome(w.Word) p
ORDER BY w.Word;


SELECT w.Word
    , p.IsPalindrome
FROM #Words w
    CROSS APPLY dbo.InlineIsPalindrome(w.Word) p
ORDER BY w.Word;

InlineIsPalindromeSürümün çok daha hızlı olmasını bekledim , ancak aşağıdaki sonuçlar bu varsayımı desteklemiyor.

Çok ifadeli TVF:

Tablo '# A1CE04C3'. Tarama sayısı 16896, mantıksal okuma 16900, fiziksel okuma 0, ileri okuma 0, lob mantık okuma 0, lob fiziksel okuma 0, lob okuma önceden 0 okur
. Tablo 'Çalışma masası'. Tarama sayısı 0, mantıksal okuma 0, fiziksel okuma 0, okuma öncesi okuma 0, lob mantıksal okuma 0, lob fiziksel okuma 0, lob okuma öncesinde 0 okuma
. Tablo '#Words'. Tarama sayısı 1, mantıksal okuma 88, fiziksel okuma 0, okuma öncesi okuma 0, lob mantıksal okuma 0, lob fiziksel okuma 0, lob okuma öncesinde okuma 0.

SQL Server Yürütme Süreleri:
CPU süresi = 1700 ms, geçen süre = 2022 ms.
SQL Server ayrıştırma ve derleme zamanı:
CPU zamanı = 0 ms, geçen süre = 0 ms.

Satır içi TVF:

Tablo 'Sayılar'. Tarama sayısı 1, mantıksal okuma 1272030, fiziksel okuma 0, okuma öncesi okuma 0, lob mantıksal okuma 0, lob fiziksel okuma 0, lob okuma öncesinde 0 okuma
. Tablo 'Çalışma masası'. Tarama sayısı 0, mantıksal okuma 0, fiziksel okuma 0, okuma öncesi okuma 0, lob mantıksal okuma 0, lob fiziksel okuma 0, lob okuma öncesinde 0 okuma
. Tablo '#Words'. Tarama sayısı 1, mantıksal okuma 88, fiziksel okuma 0, okuma öncesi okuma 0, lob mantıksal okuma 0, lob fiziksel okuma 0, lob okuma öncesinde okuma 0.

SQL Server Yürütme Süreleri:
CPU süresi = 137874 ms, geçen süre = 139415 ms.
SQL Server ayrıştırma ve derleme zamanı:
CPU zamanı = 0 ms, geçen süre = 0 ms.

Uygulama planları şöyle:

resim açıklamasını buraya girin

resim açıklamasını buraya girin

Bu durumda satır içi varyantı neden çoklu ifadeli varyanttan daha yavaş?

@AaronBertrand tarafından yapılan bir yoruma yanıt olarak, dbo.InlineIsPalindromeişlevi CTE tarafından döndürülen satırları giriş sözcüğünün uzunluğuna uyacak şekilde sınırlamak için değiştirdim :

CREATE FUNCTION dbo.InlineIsPalindrome
(
    @Word NVARCHAR(500)
)
RETURNS TABLE
WITH SCHEMABINDING
AS RETURN (
    WITH Nums AS
    (
      SELECT
        N = number
      FROM
        dbo.Numbers
      WHERE 
        number <= LEN(@Word)
    )
    SELECT
      IsPalindrome =
        CASE
          WHEN EXISTS
          (
            SELECT N
            FROM Nums
            WHERE N <= L / 2
              AND SUBSTRING(S, N, 1) <> SUBSTRING(S, 1 + L - N, 1)
          )
          THEN 0
          ELSE 1
        END
    FROM
      (SELECT LTRIM(RTRIM(@Word)), LEN(@Word)) AS v (S, L)
);

@MartinSmith'in önerdiği gibi, dbo.Numberstabloya birincil bir anahtar ve kümelenmiş dizin ekledim , bu da kesinlikle bir üretim ortamında görmeyi beklediğiniz şeylere yardımcı olur ve ona daha yakın olur.

Yukarıdaki testlerin yeniden çalıştırılması artık aşağıdaki istatistiklerle sonuçlanmaktadır:

CROSS APPLY dbo.IsPalindrome(w.Word) p:

(17424 satır etkilendi)
Tablo '# B1104853'. Tarama sayısı 17420, mantıksal okuma 17424, fiziksel okuma 0, okuma öncesi okuma 0, lob mantıksal okuma 0, lob fiziksel okuma 0, lob okuma öncesinde 0 okuma
. Tablo 'Çalışma masası'. Tarama sayısı 0, mantıksal okuma 0, fiziksel okuma 0, okuma öncesi okuma 0, lob mantıksal okuma 0, lob fiziksel okuma 0, lob okuma öncesinde 0 okuma
. Tablo '#Words'. Tarama sayısı 1, mantıksal okuma 90, fiziksel okuma 0, okuma öncesi okuma 0, lob mantıksal okuma 0, lob fiziksel okuma 0, lob okuma önceden okuma 0.

SQL Server Yürütme Süreleri:
CPU süresi = 1763 ms, geçen süre = 2192 ms.

dbo.FunctionIsPalindrome(w.Word):

(17424 satır etkilendi)
Tablo 'Çalışma Masası'. Tarama sayısı 0, mantıksal okuma 0, fiziksel okuma 0, okuma öncesi okuma 0, lob mantıksal okuma 0, lob fiziksel okuma 0, lob okuma öncesinde 0 okuma
. Tablo '#Words'. Tarama sayısı 1, mantıksal okuma 90, fiziksel okuma 0, okuma öncesi okuma 0, lob mantıksal okuma 0, lob fiziksel okuma 0, lob okuma önceden okuma 0.

SQL Server Yürütme Süreleri:
CPU süresi = 328 ms, geçen süre = 424 ms.

CROSS APPLY dbo.InlineIsPalindrome(w.Word) p:

(17424 satır etkilendi)
Tablo 'Sayılar'. Tarama sayısı 1, mantıksal okuma 237100, fiziksel okuma 0, okuma öncesi okuma 0, lob mantıksal okuma 0, lob fiziksel okuma 0, lob okuma öncesinde 0 okuma
. Tablo 'Çalışma masası'. Tarama sayısı 0, mantıksal okuma 0, fiziksel okuma 0, okuma öncesi okuma 0, lob mantıksal okuma 0, lob fiziksel okuma 0, lob okuma öncesinde 0 okuma
. Tablo '#Words'. Tarama sayısı 1, mantıksal okuma 90, fiziksel okuma 0, okuma öncesi okuma 0, lob mantıksal okuma 0, lob fiziksel okuma 0, lob okuma önceden okuma 0.

SQL Server Yürütme Süreleri:
CPU süresi = 17737 ms, geçen süre = 17946 ms.

Bunu SQL Server 2012 SP3, v11.0.6020, Developer Edition'da test ediyorum.

Birincil anahtar ve kümelenmiş dizin ile sayılarım tablosunun tanımı:

CREATE TABLE dbo.Numbers
(
    Number INT NOT NULL 
        CONSTRAINT PK_Numbers
        PRIMARY KEY CLUSTERED
);

;WITH n AS
(
    SELECT v.n 
    FROM (
        VALUES (1) 
            ,(2) 
            ,(3) 
            ,(4) 
            ,(5) 
            ,(6) 
            ,(7) 
            ,(8) 
            ,(9) 
            ,(10)
        ) v(n)
)
INSERT INTO dbo.Numbers(Number)
SELECT ROW_NUMBER() OVER (ORDER BY n1.n)
FROM n n1
    , n n2
    , n n3
    , n n4
    , n n5
    , n n6;

Yorumlar uzun tartışmalar için değildir; bu sohbet sohbete taşındı .
Paul White Monica'yı eski

Yanıtlar:


12

Sayılar tablonuz bir yığın ve her seferinde tam olarak taranıyor.

Kümelenmiş bir birincil anahtar ekleyin ve istenen aramayı elde etmek için Numberaşağıdakileri bir forceseekipucu ile deneyin .

SQL Server sadece tablonun% 27'sinin yüklemi ile eşleşeceğini tahmin ettiği için bu ipucunun gerekli olduğunu söyleyebildiğim kadarıyla (% 30 için <=ve% 27'ye kadar azaldı <>). Bu nedenle, eşleşen bir bulmadan önce sadece 3-4 satır okumak zorunda kalacak ve yarı birleştirmeden çıkabilecektir. Bu nedenle tarama seçeneği çok ucuzdur. Ama aslında herhangi bir palindrom varsa, o zaman tüm tabloyu okumak zorunda kalacak, bu yüzden bu iyi bir plan değil.

CREATE FUNCTION dbo.InlineIsPalindrome
(
    @Word NVARCHAR(500)
)
RETURNS TABLE
WITH SCHEMABINDING
AS RETURN (
    WITH Nums AS
    (
      SELECT
        N = number
      FROM
        dbo.Numbers WITH(FORCESEEK)
    )
    SELECT
      IsPalindrome =
        CASE
          WHEN EXISTS
          (
            SELECT N
            FROM Nums
            WHERE N <= L / 2
              AND SUBSTRING(S, N, 1) <> SUBSTRING(S, 1 + L - N, 1)
          )
          THEN 0
          ELSE 1
        END
    FROM
      (SELECT LTRIM(RTRIM(@Word)), LEN(@Word)) AS v (S, L)
);
GO

Bu değişikliklerle birlikte benim için uçuyor (228 ms sürüyor)

resim açıklamasını buraya girin

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.