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 Numbers
tableau 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 INSERT
résultats ci-dessus entraînent l'insertion de 16 900 lignes dans le #Words
tableau.
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 InlineIsPalindrome
version 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 à:
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.InlineIsPalindrome
fonction 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.Numbers
table, 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;