TVF multi-déclarations vs performances TVF en ligne


18

En comparant certaines des réponses à la question Palindrome (10 000 utilisateurs et plus, car j'ai supprimé la réponse), j'obtiens des résultats confus.

J'ai proposé un TVF multi-instructions, lié au schéma, qui, à mon avis, serait plus rapide que d'exécuter une fonction standard, ce qui est le cas. J'avais également l'impression que la TVF multi-déclarations serait "intégrée", bien que je me trompe sur ce point, comme vous le verrez ci-dessous. Cette question concerne la différence de performances de ces deux styles de TVF. Tout d'abord, vous devrez voir le code.

Voici le TVF multi-déclarations:

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

Le inline-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

Le Numberstableau de la fonction ci-dessus est défini comme suit:

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

Remarque: La table des nombres n'a pas d'index ni de clé primaire et contient 1 000 000 lignes.

Une table temporaire de banc d'essai:

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;

Sur mon système de test, les INSERTrésultats ci-dessus entraînent l'insertion de 16 900 lignes dans le #Wordstableau.

Pour tester les deux variantes, I SET STATISTICS IO, TIME ON;et utilisez les éléments suivants:

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;

Je m'attendais à ce que la InlineIsPalindromeversion soit beaucoup plus rapide, mais les résultats suivants ne supportent pas cette supposition.

TVF à états multiples:

Tableau '# A1CE04C3'. Nombre de balayages 16896, lectures logiques 16900, lectures physiques 0, lectures anticipées 0, lobes lectures logiques 0, lob physiques lectures 0, lob lectures anticipées lisent 0.
Tableau 'Table de travail'. Nombre de balayages 0, lectures logiques 0, lectures physiques 0, lectures anticipées 0, lectures logiques 0, lob lectures physiques 0, lob lectures anticipées 0.
Tableau '#Words'. Nombre de balayages 1, lectures logiques 88, lectures physiques 0, lectures anticipées 0, lectures logiques 0, lob lectures physiques 0, lob lectures anticipées 0.

Temps d'exécution SQL Server:
temps CPU = 1700 ms, temps écoulé = 2022 ms.
Temps d'analyse et de compilation SQL Server: temps
CPU = 0 ms, temps écoulé = 0 ms.

TVF en ligne:

Tableau «Numéros». Nombre de balayages 1, lectures logiques 1272030, lectures physiques 0, lectures anticipées 0, lectures logiques 0, lob physiques lectures 0, lob lectures anticipées 0.
Tableau 'Table de travail'. Nombre de balayages 0, lectures logiques 0, lectures physiques 0, lectures anticipées 0, lectures logiques 0, lob lectures physiques 0, lob lectures anticipées 0.
Tableau '#Words'. Nombre de balayages 1, lectures logiques 88, lectures physiques 0, lectures anticipées 0, lectures logiques 0, lob lectures physiques 0, lob lectures anticipées 0.

Temps d'exécution SQL Server:
temps CPU = 137874 ms, temps écoulé = 139415 ms.
Temps d'analyse et de compilation SQL Server: temps
CPU = 0 ms, temps écoulé = 0 ms.

Les plans d'exécution ressemblent à:

entrez la description de l'image ici

entrez la description de l'image ici

Pourquoi la variante en ligne est-elle tellement plus lente que la variante à instructions multiples, dans ce cas?

En réponse à un commentaire de @AaronBertrand, j'ai modifié la dbo.InlineIsPalindromefonction pour limiter les lignes retournées par le CTE pour correspondre à la longueur du mot d'entrée:

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)
);

Comme l'a suggéré @MartinSmith, j'ai ajouté une clé primaire et un index clusterisé à la dbo.Numberstable, ce qui aide et serait certainement plus proche de ce que l'on s'attendrait à voir dans un environnement de production.

La réexécution des tests ci-dessus entraîne désormais les statistiques suivantes:

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

(17424 ligne (s) affectée (s))
Tableau '# B1104853'. Nombre de balayages 17420, lectures logiques 17424, lectures physiques 0, lectures anticipées 0, lectures logiques 0, lob physiques lectures 0, lob lectures anticipées 0.
Tableau 'Table de travail'. Nombre de balayages 0, lectures logiques 0, lectures physiques 0, lectures anticipées 0, lectures logiques 0, lob lectures physiques 0, lob lectures anticipées 0.
Tableau '#Words'. Nombre de balayages 1, lectures logiques 90, lectures physiques 0, lectures anticipées 0, lectures logiques 0, lob lectures physiques 0, lob lectures anticipées 0.

Temps d'exécution SQL Server:
temps CPU = 1763 ms, temps écoulé = 2192 ms.

dbo.FunctionIsPalindrome(w.Word):

(17424 ligne (s) affectée (s))
Table 'Table de travail'. Nombre de balayages 0, lectures logiques 0, lectures physiques 0, lectures anticipées 0, lectures logiques 0, lob lectures physiques 0, lob lectures anticipées 0.
Tableau '#Words'. Nombre de balayages 1, lectures logiques 90, lectures physiques 0, lectures anticipées 0, lectures logiques 0, lob lectures physiques 0, lob lectures anticipées 0.

Temps d'exécution SQL Server:
temps CPU = 328 ms, temps écoulé = 424 ms.

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

(17424 ligne (s) affectée (s))
Tableau 'Numéros'. Nombre de balayages 1, lectures logiques 237100, lectures physiques 0, lectures anticipées 0, lectures logiques 0, lob lectures physiques 0, lob lectures anticipées 0.
Tableau 'Table de travail'. Nombre de balayages 0, lectures logiques 0, lectures physiques 0, lectures anticipées 0, lectures logiques 0, lob lectures physiques 0, lob lectures anticipées 0.
Tableau '#Words'. Nombre de balayages 1, lectures logiques 90, lectures physiques 0, lectures anticipées 0, lectures logiques 0, lob lectures physiques 0, lob lectures anticipées 0.

Temps d'exécution SQL Server:
temps CPU = 17737 ms, temps écoulé = 17946 ms.

Je teste cela sur SQL Server 2012 SP3, v11.0.6020, Developer Edition.

Voici la définition de ma table de nombres, avec la clé primaire et l'index clusterisé:

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;

Les commentaires ne sont pas pour une discussion approfondie; cette conversation a été déplacée vers le chat .
Paul White réintègre Monica

Réponses:


12

Votre table de nombres est un tas et est potentiellement analysée à chaque fois.

Ajoutez une clé primaire en cluster Numberet essayez ce qui suit avec un forceseekindice pour obtenir la recherche souhaitée.

Pour autant que je sache, cet indice est nécessaire car SQL Server estime simplement que 27% de la table correspondra au prédicat (30% pour le <=et réduit à 27% par le <>). Et par conséquent, il n'aura qu'à lire 3-4 lignes avant d'en trouver une qui correspond et il peut quitter la semi-jointure. L'option de numérisation est donc très peu coûteuse. Mais en fait, si des palindromes existent, il faudra lire tout le tableau, ce n'est donc pas un bon plan.

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

Avec ces changements en place, il vole pour moi (prend 228 ms)

entrez la description de l'image ici

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.