Comparando algunas de las respuestas en la pregunta de Palindrome (solo 10k + usuarios, ya que he eliminado la respuesta), obtengo resultados confusos.
Propuse un TVF de múltiples enunciados y vinculado a un esquema que pensé que sería más rápido que ejecutar una función estándar, que es. También tenía la impresión de que el TVF de varias declaraciones estaría "en línea", aunque me equivoco en ese aspecto, como verá a continuación. Esta pregunta es sobre la diferencia de rendimiento de esos dos estilos de TVF. Primero, necesitarás ver el código.
Aquí está el TVF multi-declaración:
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
El TVF en línea:
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
La Numbers
tabla en la función anterior se define como:
CREATE TABLE dbo.Numbers
(
Number INT NOT NULL
);
Nota: La tabla de números no tiene índices ni clave principal, y contiene 1,000,000 de filas.
Una mesa temporal de banco de pruebas:
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;
En mi sistema de prueba, lo anterior INSERT
da como resultado que se inserten 16.900 filas en la #Words
tabla.
Para probar las dos variaciones, SET STATISTICS IO, TIME ON;
utilizo lo siguiente:
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;
Esperaba que la InlineIsPalindrome
versión fuera significativamente más rápida, sin embargo, los siguientes resultados no respaldan esa suposición.
TVF multi-declaración:
Tabla '# A1CE04C3'. Cuenta de escaneo 16896, lecturas lógicas 16900, lecturas físicas 0, lecturas de lectura anticipada 0, lecturas lógicas lob 0, lecturas físicas lob 0, lecturas de lectura lob 0.
Tabla 'Tabla de trabajo'. Recuento de escaneo 0, lecturas lógicas 0, lecturas físicas 0, lecturas de lectura anticipada 0, lecturas lógicas lob 0, lecturas físicas lob 0, lecturas de lectura lob 0. 0.
Tabla '#Palabras'. Cuenta de escaneo 1, lecturas lógicas 88, lecturas físicas 0, lecturas de lectura anticipada 0, lecturas lógicas lob 0, lecturas físicas lob 0, lecturas de lectura lob 0.Tiempos de ejecución de SQL Server:
tiempo de CPU = 1700 ms, tiempo transcurrido = 2022 ms.
Tiempo de análisis y compilación de SQL Server: tiempo de
CPU = 0 ms, tiempo transcurrido = 0 ms.
TVF en línea:
Tabla 'Números'. Cuenta de escaneo 1, lecturas lógicas 1272030, lecturas físicas 0, lecturas anticipadas 0, lecturas lógicas lob 0, lecturas físicas lob 0, lecturas anticipadas lob 0.
Tabla 'Mesa de trabajo'. Recuento de escaneo 0, lecturas lógicas 0, lecturas físicas 0, lecturas de lectura anticipada 0, lecturas lógicas lob 0, lecturas físicas lob 0, lecturas de lectura lob 0. 0.
Tabla '#Palabras'. Cuenta de escaneo 1, lecturas lógicas 88, lecturas físicas 0, lecturas de lectura anticipada 0, lecturas lógicas lob 0, lecturas físicas lob 0, lecturas de lectura lob 0.Tiempos de ejecución de SQL Server:
tiempo de CPU = 137874 ms, tiempo transcurrido = 139415 ms.
Tiempo de análisis y compilación de SQL Server: tiempo de
CPU = 0 ms, tiempo transcurrido = 0 ms.
Los planes de ejecución se ven así:
¿Por qué la variante en línea es mucho más lenta que la variante de varias instrucciones, en este caso?
En respuesta a un comentario de @AaronBertrand, he modificado la dbo.InlineIsPalindrome
función para limitar las filas devueltas por el CTE para que coincida con la longitud de la palabra de entrada:
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)
);
Como sugirió @MartinSmith, agregué una clave primaria y un índice agrupado a la dbo.Numbers
tabla, lo que ciertamente ayuda y estaría más cerca de lo que uno esperaría ver en un entorno de producción.
Volver a ejecutar las pruebas anteriores ahora da como resultado las siguientes estadísticas:
CROSS APPLY dbo.IsPalindrome(w.Word) p
:
(17424 filas afectadas)
Tabla '# B1104853'. Recuento de escaneos 17420, lecturas lógicas 17424, lecturas físicas 0, lecturas de lectura anticipada 0, lecturas lógicas lob 0, lecturas físicas lob 0, lecturas de lectura lob 0.
Tabla 'Mesa de trabajo'. Recuento de escaneo 0, lecturas lógicas 0, lecturas físicas 0, lecturas de lectura anticipada 0, lecturas lógicas lob 0, lecturas físicas lob 0, lecturas de lectura lob 0. 0.
Tabla '#Palabras'. Cuenta de escaneo 1, lecturas lógicas 90, lecturas físicas 0, lecturas de lectura anticipada 0, lecturas lógicas lob 0, lecturas físicas lob 0, lecturas lob de lectura anticipada 0.Tiempos de ejecución de SQL Server:
tiempo de CPU = 1763 ms, tiempo transcurrido = 2192 ms.
dbo.FunctionIsPalindrome(w.Word)
:
(17424 filas afectadas)
Tabla 'Mesa de trabajo'. Recuento de escaneo 0, lecturas lógicas 0, lecturas físicas 0, lecturas de lectura anticipada 0, lecturas lógicas lob 0, lecturas físicas lob 0, lecturas de lectura lob 0. 0.
Tabla '#Palabras'. Cuenta de escaneo 1, lecturas lógicas 90, lecturas físicas 0, lecturas de lectura anticipada 0, lecturas lógicas lob 0, lecturas físicas lob 0, lecturas lob de lectura anticipada 0.Tiempos de ejecución de SQL Server:
tiempo de CPU = 328 ms, tiempo transcurrido = 424 ms.
CROSS APPLY dbo.InlineIsPalindrome(w.Word) p
:
(17424 filas afectadas)
Tabla 'Números'. Cuenta de escaneo 1, lecturas lógicas 237100, lecturas físicas 0, lecturas anticipadas 0, lecturas lógicas lob 0, lecturas físicas lob 0, lecturas anticipadas lob 0.
Tabla 'Tabla de trabajo'. Recuento de escaneo 0, lecturas lógicas 0, lecturas físicas 0, lecturas de lectura anticipada 0, lecturas lógicas lob 0, lecturas físicas lob 0, lecturas de lectura lob 0. 0.
Tabla '#Palabras'. Cuenta de escaneo 1, lecturas lógicas 90, lecturas físicas 0, lecturas de lectura anticipada 0, lecturas lógicas lob 0, lecturas físicas lob 0, lecturas lob de lectura anticipada 0.Tiempos de ejecución de SQL Server:
tiempo de CPU = 17737 ms, tiempo transcurrido = 17946 ms.
Estoy probando esto en SQL Server 2012 SP3, v11.0.6020, Developer Edition.
Aquí está la definición de mi tabla de números, con la clave primaria y el índice agrupado:
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;
fuente
Respuestas:
Su tabla de números es un montón y potencialmente se está escaneando completamente cada vez.
Agregue una clave principal en clúster
Number
e intente lo siguiente con unaforceseek
pista para obtener la búsqueda deseada.Por lo que puedo decir, esta sugerencia es necesaria ya que SQL Server solo estima que el 27% de la tabla coincidirá con el predicado (30% para el
<=
y reducido al 27% por el<>
). Y por lo tanto, solo tendrá que leer 3-4 filas antes de encontrar una que coincida y pueda salir de la semiunión. Entonces, la opción de escaneo tiene un costo muy bajo. Pero, de hecho, si existen palíndromos, entonces tendrá que leer toda la tabla, por lo que este no es un buen plan.Con esos cambios en su lugar, vuela para mí (toma 228 ms)
fuente