Sqlserver
 sql >> Datenbank >  >> RDS >> Sqlserver

Der satzbasierte Plan wird mit vielen Bedingungen langsamer ausgeführt als die Skalarwertfunktion

Der Schlüsselwortbegriff hier ist INLINE TABELLE BEWERTETE FUNKTIONEN . Sie haben zwei Arten von T-SQL-Tabellenwertfunktionen:Multi-Anweisung und Inline. Wenn Ihre T-SQL-Funktion mit einer BEGIN-Anweisung beginnt, wird sie Mist sein - skalar oder anders. Sie können eine temporäre Tabelle nicht in eine inline einfügen Tabellenwertfunktion, also gehe ich davon aus, dass Sie von einer skalaren zu einer Multi-Statement-Tabellenwertfunktion gewechselt sind, was wahrscheinlich schlimmer sein wird.

Ihre Inline-Tabellenwertfunktion (iTVF) sollte in etwa so aussehen:

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;

Beachten Sie, dass in dem von Ihnen geposteten Code Ihr DATEDIFF steht -Anweisung fehlt der datepart Parameter. Sollte etwa so aussehen:

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

Etwas weiter gehen - es ist wichtig zu verstehen, warum iTVFs besser sind als skalare benutzerdefinierte T-SQL-Funktionen. Dies liegt nicht daran, dass Tabellenwertfunktionen schneller sind als Skalarwertfunktionen, sondern daran, dass die Microsoft-Implementierung von T-SQL-Inlinefunktionen schneller ist als die Implementierung von T-SQL-Funktionen, die nicht inline sind. Beachten Sie die folgenden drei Funktionen, die dasselbe tun:

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

Nun zu einigen Beispieldaten und Leistungstests:

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

Ergebnisse:

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

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

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

Der Skalar udf lief für 2,7 Sekunden, 41 Sekunden für den mtvf und 0,153 Sekunden für den iTVF. Um zu verstehen warum, schauen wir uns die geschätzten Ausführungspläne an:

Sie sehen dies nicht, wenn Sie sich den tatsächlichen Ausführungsplan ansehen, aber mit dem skalaren udf und mtvf ruft der Optimierer für jede Zeile eine schlecht ausgeführte Subroutine auf; der iTVF nicht. Zitat von Paul Whites Karrierewechsel Artikel über BEWERBEN Paulus schreibt:

Mit anderen Worten, iTVF ermöglicht es dem Optimierer, die Abfrage auf eine Weise zu optimieren, die einfach nicht möglich ist, wenn all dieser andere Code ausgeführt werden muss. Eines von vielen anderen Beispielen, warum iTVFs überlegen sind, ist, dass sie die einzigen der drei oben genannten Funktionstypen sind, die Parallelität zulassen. Lassen Sie uns jede Funktion noch einmal ausführen, diesmal mit aktiviertem Actual Execution Plan und mit Traceflag 8649 (das einen parallelen Ausführungsplan erzwingt):

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

Ausführungspläne:

Diese Pfeile, die Sie für den Ausführungsplan des iTVF sehen, sind Parallelität - alle Ihre CPUs (oder so viele wie der MAXDOP Ihrer SQL-Instanz Einstellungen erlauben) zusammenarbeiten. T-SQL-Skalar- und MTVF-UDFs können das nicht. Wenn Microsoft skalare Inline-UDFs einführt, würde ich diese für das, was Sie tun, vorschlagen, aber bis dahin:Wenn Leistung das ist, wonach Sie suchen, dann ist Inline der einzige Weg, und dafür sind iTVFs das einzige Spiel in der Stadt.

Beachten Sie, dass ich immer wieder T-SQL betont habe Wenn wir über Funktionen sprechen ... CLR-Skalar- und Tabellenwertfunktionen können ganz gut sein, aber das ist ein anderes Thema.