sql >> Databasteknik >  >> RDS >> Sqlserver

Uppsättningsbaserad plan körs långsammare än skalärvärderad funktion med många villkor

Sökordstermen här är INLINE TABELL VÄRDERADE FUNKTIONER . Du har två typer av T-SQL-värderade funktioner:multi-statement och inline. Om din T-SQL-funktion börjar med en BEGIN-sats kommer det att bli skit - skalärt eller annat. Du kan inte få in en tillfällig tabell i en inline tabellvärderad funktion så jag antar att du gick från skalär till mutli-sats tabellvärderad funktion som förmodligen kommer att vara sämre.

Din inline-tabellvärderade funktion (iTVF) bör se ut ungefär så här:

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;

Observera att din DATEDIFF i koden du postade satsen saknar datepart parameter. Om ska se ut ungefär som:

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

Om vi ​​går lite längre - det är viktigt att förstå varför iTVF:er är bättre än T-SQL skalära användardefinierade funktioner. Det är inte för att tabellvärderade funktioner är snabbare än skalärvärderade funktioner, det beror på att Microsofts implementering av T-SQL inline-funktioner är snabbare än deras implementering av T-SQL-funktioner som inte är inline. Observera följande tre funktioner som gör samma sak:

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

Nu till några exempeldata och prestandatest:

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

Resultat:

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

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

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

Den skalära udf kördes i 2,7 sekunder, 41 sekunder för mtvf och 0,153 sekunder för iTVF. För att förstå varför låt oss titta på de beräknade genomförandeplanerna:

Du ser inte detta när du tittar på den faktiska exekveringsplanen, men med den skalära udf och mtvf anropar optimeraren någon dåligt utförd subrutin för varje rad; iTVF gör det inte. Citerar Paul Whites karriärförändring artikel om APPLY Paul skriver:

Med andra ord, iTVF gör det möjligt att optimera för att optimera frågan på sätt som helt enkelt inte är möjliga när all den andra koden måste exekveras. Ett av många andra exempel på varför iTVF är överlägsna är att de är den enda av de tre ovannämnda funktionstyperna som tillåter parallellism. Låt oss köra varje funktion en gång till, den här gången med den faktiska exekveringsplanen påslagen och med spårflagga 8649 (som tvingar fram en parallell exekveringsplan):

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

Utförandeplaner:

De pilar du ser för iTVF:s exekveringsplan är parallellitet - alla dina CPU:er (eller lika många som din SQL-instans:s MAXDOP inställningar tillåter) att arbeta tillsammans. T-SQL scalar och mtvf UDF kan inte göra det. När Microsoft introducerar inline skalära UDF:er skulle jag föreslå dem för det du gör, men tills dess:om prestanda är vad du letar efter är inline den enda vägen att gå och för det är iTVFs det enda spelet i staden.

Observera att jag kontinuerligt betonade T-SQL när vi pratar om funktioner... CLR Scalar och Table-värderade funktioner kan vara bra men det är ett annat ämne.




  1. Använder JQuery för att infoga värden i mySQL

  2. Få fältvärdet med en markör

  3. MySQL datumjämförelseproblem?

  4. Förvandla datum till datumintervall i MYSQL --- hur man hanterar luckor i datumen