sql >> Base de Datos >  >> RDS >> Sqlserver

El plan basado en conjuntos se ejecuta más lentamente que la función de valor escalar con muchas condiciones

El término de palabra clave aquí es INLINE FUNCIONES CON VALOR DE TABLA . Tiene dos tipos de funciones con valores tabulados de T-SQL:declaraciones múltiples y en línea. Si su función T-SQL comienza con una declaración BEGIN, entonces será una mierda, escalar o de otra manera. No puede obtener una tabla temporal en un en línea función de valor de tabla, así que asumo que pasó de escalar a función de valor de tabla de declaración múltiple, lo que probablemente será peor.

Su función de valor de tabla en línea (iTVF) debería verse así:

CREATE FUNCTION [dbo].[Compute_value]
(
  @alpha FLOAT,
  @bravo FLOAT,
  @charle FLOAT,
  @delta FLOAT
)
RETURNS TABLE WITH SCHEMABINDING AS RETURN
SELECT newValue = 
  CASE WHEN @alpha IS NULL OR @alpha = 0 OR @delta IS NULL OR @delta = 0 THEN 0
       WHEN @bravo IS NULL OR @bravo <= 0 THEN 100
       ELSE @alpha * POWER((100 / @delta), 
             (-2 * POWER(@charle * @bravo, DATEDIFF(<unit of measurement>,GETDATE(),'1/1/2000')/365)))
  END
GO;

Tenga en cuenta que, en el código que publicó, su DATEDIFF a la declaración le falta el datepart parámetro. Si debería verse algo como:

@x int = DATEDIFF(DAY, GETDATE(),'1/1/2000')   

Yendo un poco más allá:es importante comprender por qué los iTVF son mejores que las funciones definidas por el usuario con valores escalares de T-SQL. No es porque las funciones con valores de tabla sean más rápidas que las funciones con valores escalares, es porque la implementación de funciones en línea de T-SQL de Microsoft es más rápida que su implementación de funciones de T-SQL que no están en línea. Tenga en cuenta las siguientes tres funciones que hacen lo mismo:

-- Scalar version
CREATE FUNCTION dbo.Compute_value_scalar
(
  @alpha FLOAT,
  @bravo FLOAT,
  @charle FLOAT,
  @delta FLOAT
)
RETURNS FLOAT
AS
BEGIN
    IF @alpha IS NULL OR @alpha = 0 OR @delta IS NULL OR @delta = 0 
    RETURN 0

    IF @bravo IS NULL OR @bravo <= 0
        RETURN 100

    IF (@charle + @delta) / @bravo <= 0
        RETURN 100
    DECLARE @x int = DATEDIFF(dd, GETDATE(),'1/1/2000')     
    RETURN @alpha * POWER((100 / @delta), (-2 * POWER(@charle * @bravo, @x/365)))
END
GO

-- multi-statement table valued function 
CREATE FUNCTION dbo.Compute_value_mtvf
(
  @alpha FLOAT,
  @bravo FLOAT,
  @charle FLOAT,
  @delta FLOAT
)
RETURNS  @sometable TABLE (newValue float) AS 
    BEGIN
    INSERT @sometable VALUES
(
  CASE WHEN @alpha IS NULL OR @alpha = 0 OR @delta IS NULL OR @delta = 0 THEN 0
       WHEN @bravo IS NULL OR @bravo <= 0 THEN 100
       ELSE @alpha * POWER((100 / @delta), 
             (-2 * POWER(@charle * @bravo, DATEDIFF(DAY,GETDATE(),'1/1/2000')/365)))
  END
)
RETURN;
END
GO

-- INLINE table valued function
CREATE FUNCTION dbo.Compute_value_itvf
(
  @alpha FLOAT,
  @bravo FLOAT,
  @charle FLOAT,
  @delta FLOAT
)
RETURNS TABLE WITH SCHEMABINDING AS RETURN
SELECT newValue = 
  CASE WHEN @alpha IS NULL OR @alpha = 0 OR @delta IS NULL OR @delta = 0 THEN 0
       WHEN @bravo IS NULL OR @bravo <= 0 THEN 100
       ELSE @alpha * POWER((100 / @delta), 
             (-2 * POWER(@charle * @bravo, DATEDIFF(DAY,GETDATE(),'1/1/2000')/365)))
  END
GO

Ahora, algunos datos de muestra y pruebas de rendimiento:

SET NOCOUNT ON;
CREATE TABLE #someTable (alpha FLOAT, bravo FLOAT, charle FLOAT, delta FLOAT);
INSERT #someTable
SELECT TOP (100000)
  abs(checksum(newid())%10)+1, abs(checksum(newid())%10)+1, 
  abs(checksum(newid())%10)+1, abs(checksum(newid())%10)+1
FROM sys.all_columns a, sys.all_columns b;

PRINT char(10)+char(13)+'scalar'+char(10)+char(13)+replicate('-',60);
GO
DECLARE @st datetime = getdate(), @z float;

SELECT @z = dbo.Compute_value_scalar(t.alpha, t.bravo, t.charle, t.delta)
FROM #someTable t;

PRINT DATEDIFF(ms, @st, getdate());
GO

PRINT char(10)+char(13)+'mtvf'+char(10)+char(13)+replicate('-',60);
GO
DECLARE @st datetime = getdate(), @z float;

SELECT @z = f.newValue
FROM #someTable t
CROSS APPLY dbo.Compute_value_mtvf(t.alpha, t.bravo, t.charle, t.delta) f;

PRINT DATEDIFF(ms, @st, getdate());
GO

PRINT char(10)+char(13)+'itvf'+char(10)+char(13)+replicate('-',60);
GO
DECLARE @st datetime = getdate(), @z float;

SELECT @z = f.newValue
FROM #someTable t
CROSS APPLY dbo.Compute_value_itvf(t.alpha, t.bravo, t.charle, t.delta) f;

PRINT DATEDIFF(ms, @st, getdate());
GO

Resultados:

scalar
------------------------------------------------------------
2786

mTVF
------------------------------------------------------------
41536

iTVF
------------------------------------------------------------
153

El udf escalar funcionó durante 2,7 segundos, 41 segundos para el mtvf y 0,153 segundos para el iTVF. Para entender por qué, veamos los planes de ejecución estimados:

No ve esto cuando mira el plan de ejecución real pero, con el escalar udf y mtvf, el optimizador llama a alguna subrutina mal ejecutada para cada fila; el iTVF no lo hace. Citando el cambio de carrera de Paul White artículo sobre APLICAR Pablo escribe:

En otras palabras, iTVF habilita al optimizador para optimizar la consulta de maneras que simplemente no son posibles cuando se necesita ejecutar todo el otro código. Uno de los muchos otros ejemplos de por qué los iTVF son superiores es que son los únicos de los tres tipos de funciones mencionados anteriormente que permiten el paralelismo. Ejecutemos cada función una vez más, esta vez con el plan de ejecución real activado y con la marca de seguimiento 8649 (que fuerza un plan de ejecución en paralelo):

-- don't need so many rows for this test
TRUNCATE TABLE #sometable;
INSERT #someTable 
SELECT TOP (10)
  abs(checksum(newid())%10)+1, abs(checksum(newid())%10)+1, 
  abs(checksum(newid())%10)+1, abs(checksum(newid())%10)+1
FROM sys.all_columns a;

DECLARE @x float;

SELECT TOP (10) @x = dbo.Compute_value_scalar(t.alpha, t.bravo, t.charle, t.delta)
FROM #someTable t
ORDER BY dbo.Compute_value_scalar(t.alpha, t.bravo, t.charle, t.delta)
OPTION (QUERYTRACEON 8649);

SELECT TOP (10)  @x = f.newValue
FROM #someTable t
CROSS APPLY dbo.Compute_value_mtvf(t.alpha, t.bravo, t.charle, t.delta) f
ORDER BY f.newValue
OPTION (QUERYTRACEON 8649);

SELECT @x = f.newValue
FROM #someTable t
CROSS APPLY dbo.Compute_value_itvf(t.alpha, t.bravo, t.charle, t.delta) f
ORDER BY f.newValue
OPTION (QUERYTRACEON 8649);

Planes de ejecución:

Esas flechas que ve para el plan de ejecución de iTVF son paralelismo:todas sus CPU (o tantas como MAXDOP de su instancia de SQL la configuración lo permite) trabajando juntos. T-SQL escalar y mtvf UDF no pueden hacer eso. Cuando Microsoft presente UDF escalares en línea, sugeriría esos para lo que está haciendo pero, hasta entonces:si el rendimiento es lo que está buscando, entonces en línea es la única forma de hacerlo y, para eso, los iTVF son el único juego. en la ciudad.

Tenga en cuenta que insistí continuamente en T-SQL cuando se habla de funciones... CLR escalar y las funciones con valores de tabla pueden estar bien, pero ese es un tema diferente.