Beim Vergleich einiger Antworten auf die Palindrome-Frage (ab 10.000 Benutzern, da ich die Antwort gelöscht habe) erhalte ich verwirrende Ergebnisse.
Ich schlug eine mehranweisungsfähige, schemagebundene TVF vor, die meiner Meinung nach schneller ist als die Ausführung einer Standardfunktion, die es ist. Ich hatte auch den Eindruck, dass die TVF mit mehreren Aussagen "inline" sein würde, obwohl ich in dieser Hinsicht falsch liege, wie Sie weiter unten sehen werden. Bei dieser Frage geht es um den Leistungsunterschied dieser beiden TVF-Stile. Zuerst müssen Sie den Code sehen.
Hier ist die mehrteilige TVF:
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
Der 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
Die Numbers
Tabelle in der obigen Funktion ist wie folgt definiert:
CREATE TABLE dbo.Numbers
(
Number INT NOT NULL
);
Hinweis: Die Zahlentabelle enthält keine Indizes und keinen Primärschlüssel und enthält 1.000.000 Zeilen.
Ein temporärer Tisch auf dem Prüfstand:
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;
Auf meinem Testsystem führen die obigen INSERT
Ergebnisse dazu, dass 16.900 Zeilen in die #Words
Tabelle eingefügt werden .
Zum Testen der beiden Varianten verwende ich SET STATISTICS IO, TIME ON;
Folgendes:
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;
Ich habe erwartet, dass die InlineIsPalindrome
Version deutlich schneller sein wird, aber die folgenden Ergebnisse stützen diese Annahme nicht.
Mehrfachaussage TVF:
Tabelle '# A1CE04C3'. Scananzahl 16896, logische Lesevorgänge 16900, physische Lesevorgänge 0, Vorauslesevorgänge 0, logische Lobs-Lesevorgänge 0, physikalische Lobs-Lesevorgänge 0,
Lobs-Vorauslesevorgänge 0. Tabelle 'Arbeitstabelle'. Scananzahl 0, logische Lesevorgänge 0, physische Lesevorgänge 0,
Vorauslesevorgänge 0, logische Lobs- Lesevorgänge 0, physikalische Lobs -Lesevorgänge 0 , Lobs-Vorauslesevorgänge 0. Tabelle '#Words'. Scananzahl 1, logische Lesevorgänge 88, physische Lesevorgänge 0, Vorauslesevorgänge 0, logische Lobs-Lesevorgänge 0, physikalische Lobs-Lesevorgänge 0, Lobs-Vorauslesevorgänge 0.SQL Server-Ausführungszeiten:
CPU-Zeit = 1700 ms, verstrichene Zeit = 2022 ms.
SQL Server-Analyse- und Kompilierungszeit:
CPU-Zeit = 0 ms, verstrichene Zeit = 0 ms.
Inline-TVF:
Tabelle 'Zahlen'. Scananzahl 1, logische Lesevorgänge 1272030, physische Lesevorgänge 0, Vorauslesevorgänge 0, logische Lobs-Lesevorgänge 0, physikalische Lobs-Lesevorgänge 0,
Lobs-Vorauslesevorgänge 0. Tabelle 'Arbeitstabelle'. Scananzahl 0, logische Lesevorgänge 0, physische Lesevorgänge 0,
Vorauslesevorgänge 0, logische Lobs- Lesevorgänge 0, physikalische Lobs -Lesevorgänge 0 , Lobs-Vorauslesevorgänge 0. Tabelle '#Words'. Scananzahl 1, logische Lesevorgänge 88, physische Lesevorgänge 0, Vorauslesevorgänge 0, logische Lobs-Lesevorgänge 0, physikalische Lobs-Lesevorgänge 0, Lobs-Vorauslesevorgänge 0.SQL Server-Ausführungszeiten:
CPU-Zeit = 137874 ms, verstrichene Zeit = 139415 ms.
SQL Server-Analyse- und Kompilierungszeit:
CPU-Zeit = 0 ms, verstrichene Zeit = 0 ms.
Die Ausführungspläne sehen folgendermaßen aus:
Warum ist die Inline-Variante in diesem Fall so viel langsamer als die Variante mit mehreren Anweisungen?
Als Antwort auf einen Kommentar von @AaronBertrand habe ich die dbo.InlineIsPalindrome
Funktion geändert , um die vom CTE zurückgegebenen Zeilen auf die Länge des Eingabeworts zu beschränken:
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)
);
Wie @MartinSmith vorschlug, habe ich der dbo.Numbers
Tabelle einen Primärschlüssel und einen Clustered-Index hinzugefügt , was sicherlich hilfreich ist und näher an dem liegt, was man in einer Produktionsumgebung erwarten würde.
Das erneute Ausführen der obigen Tests führt nun zu den folgenden Statistiken:
CROSS APPLY dbo.IsPalindrome(w.Word) p
:
(17424 betroffene Zeile (n))
Tabelle '# B1104853'. Scan-Anzahl 17420, logische Lesevorgänge 17424, physische Lesevorgänge 0, Vorauslesevorgänge 0, logische Lobs-Lesevorgänge 0, physikalische Lobs-Lesevorgänge 0,
Lobs-Vorauslesevorgänge 0. Tabelle 'Arbeitstabelle'. Scananzahl 0, logische Lesevorgänge 0, physische Lesevorgänge 0,
Vorauslesevorgänge 0, logische Lobs- Lesevorgänge 0, physikalische Lobs -Lesevorgänge 0 , Lobs-Vorauslesevorgänge 0. Tabelle '#Words'. Scananzahl 1, logische Lesevorgänge 90, physische Lesevorgänge 0, Vorauslesevorgänge 0, logische Lobs-Lesevorgänge 0, physikalische Lobs-Lesevorgänge 0, Lobs-Vorauslesevorgänge 0.SQL Server-Ausführungszeiten:
CPU-Zeit = 1763 ms, verstrichene Zeit = 2192 ms.
dbo.FunctionIsPalindrome(w.Word)
:
(17424 betroffene Zeile (n))
Tabelle 'Arbeitstisch'. Scananzahl 0, logische Lesevorgänge 0, physische Lesevorgänge 0,
Vorauslesevorgänge 0, logische Lobs- Lesevorgänge 0, physikalische Lobs -Lesevorgänge 0 , Lobs-Vorauslesevorgänge 0. Tabelle '#Words'. Scananzahl 1, logische Lesevorgänge 90, physische Lesevorgänge 0, Vorauslesevorgänge 0, logische Lobs-Lesevorgänge 0, physikalische Lobs-Lesevorgänge 0, Lobs-Vorauslesevorgänge 0.SQL Server-Ausführungszeiten:
CPU-Zeit = 328 ms, verstrichene Zeit = 424 ms.
CROSS APPLY dbo.InlineIsPalindrome(w.Word) p
:
(17424 betroffene Zeile (n))
Tabelle 'Numbers'. Scananzahl 1, logische Lesevorgänge 237100, physische Lesevorgänge 0, Vorlesevorgänge 0, logische Vorlesevorgänge 0, physische
Vorlesevorgänge 0 , Vorlesevorgänge 0. Tabelle 'Arbeitstabelle'. Scananzahl 0, logische Lesevorgänge 0, physische Lesevorgänge 0,
Vorauslesevorgänge 0, logische Lobs- Lesevorgänge 0, physikalische Lobs -Lesevorgänge 0 , Lobs-Vorauslesevorgänge 0. Tabelle '#Words'. Scananzahl 1, logische Lesevorgänge 90, physische Lesevorgänge 0, Vorauslesevorgänge 0, logische Lobs-Lesevorgänge 0, physikalische Lobs-Lesevorgänge 0, Lobs-Vorauslesevorgänge 0.SQL Server-Ausführungszeiten:
CPU-Zeit = 17737 ms, verstrichene Zeit = 17946 ms.
Ich teste dies auf SQL Server 2012 SP3, v11.0.6020, Developer Edition.
Hier ist die Definition meiner Nummerntabelle mit dem Primärschlüssel und dem Clustered-Index:
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;
quelle
Antworten:
Ihre Zahlentabelle ist ein Haufen und wird möglicherweise jedes Mal vollständig gescannt.
Fügen Sie einen gruppierten Primärschlüssel hinzu
Number
und versuchen Sie Folgendes mit einemforceseek
Hinweis, um die gewünschte Suche zu erhalten.Soweit ich das beurteilen kann, wird dieser Hinweis benötigt, da SQL Server nur schätzt, dass 27% der Tabelle mit dem Prädikat übereinstimmen (30% für das
<=
und bis auf 27% für das<>
). Und deshalb muss es nur 3-4 Zeilen lesen, bevor es eine passende findet, und es kann den Semi-Join verlassen. Die Scanoption ist also sehr günstig. Wenn Palindrome vorhanden sind, muss die gesamte Tabelle gelesen werden, sodass dies kein guter Plan ist.Mit diesen Änderungen fliegt es für mich (dauert 228ms)
quelle