Sqlserver
 sql >> Baza danych >  >> RDS >> Sqlserver

Plan oparty na zestawach działa wolniej niż funkcja o wartości skalarnej z wieloma warunkami

Słowo kluczowe to tutaj INLINE WARTOŚCIOWE FUNKCJE TABELI . Dostępne są dwa typy funkcji T-SQL z wartościami w tabeli:wieloinstrukcyjne i wbudowane. Jeśli twoja funkcja T-SQL zaczyna się od instrukcji BEGIN, będzie to bzdura - skalarna lub inna. Nie możesz umieścić tabeli tymczasowej w inline funkcja o wartości tabeli, więc zakładam, że przeszedłeś od skalarnej do funkcji o wartości tabeli z wieloma instrukcjami, która prawdopodobnie będzie gorsza.

Twoja funkcja z wartościami w tabeli inline (iTVF) powinna wyglądać mniej więcej tak:

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;

Zwróć uwagę, że w opublikowanym kodzie Twój DATEDIFF w instrukcji brakuje datepart parametr. Jeśli powinno wyglądać mniej więcej tak:

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

Idąc trochę dalej - ważne jest, aby zrozumieć, dlaczego iTVF są lepsze niż funkcje zdefiniowane przez użytkownika o wartości skalarnej T-SQL. Nie jest tak dlatego, że funkcje o wartościach tabelarycznych są szybsze niż funkcje o wartościach skalarnych, ale dlatego, że implementacja przez Microsoft funkcji wbudowanych T-SQL jest szybsza niż ich implementacja funkcji T-SQL, które nie są wbudowane. Zwróć uwagę na następujące trzy funkcje, które robią to samo:

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

Teraz kilka przykładowych danych i testu wydajności:

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

Wyniki:

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

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

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

Skalarny UDF działał przez 2,7 sekundy, 41 sekund dla mtvf i 0,153 sekundy dla iTVF. Aby zrozumieć, dlaczego przyjrzyjmy się szacunkowym planom wykonania:

Nie widać tego, gdy patrzysz na rzeczywisty plan wykonania, ale w przypadku skalarnych udf i mtvf optymalizator wywołuje źle wykonany podprogram dla każdego wiersza; iTVF nie. Cytując zmianę kariery Paula White'a artykuł o APLIKUJ Paweł pisze:

Innymi słowy, iTVF umożliwia optymalizatorowi optymalizację zapytania w sposób, który po prostu nie jest możliwy, gdy cały inny kod musi zostać wykonany. Jednym z wielu innych przykładów, dlaczego iTVF są lepsze, jest to, że są jedynymi z trzech wyżej wymienionych typów funkcji, które umożliwiają równoległość. Uruchommy każdą funkcję jeszcze raz, tym razem z włączonym planem Actual Execution i z traceflag 8649 (co wymusza równoległy plan wykonania):

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

Plany wykonania:

Te strzałki, które widzisz dla planu wykonania iTVF, to równoległość - wszystkie twoje procesory (lub tyle, ile MAXDOP twojej instancji SQL ustawienia pozwalają) pracować razem. UDF skalarny T-SQL i mtvf nie mogą tego zrobić. Kiedy Microsoft wprowadza wbudowane skalarne UDF, sugerowałbym je dla tego, co robisz, ale do tego czasu:jeśli wydajność jest tym, czego szukasz, to inline jest jedyną drogą, a do tego iTVF są jedyną grą w mieście.

Zauważ, że stale kładłem nacisk na T-SQL kiedy mówimy o funkcjach... Funkcje CLR Scalar i Table mogą być w porządku, ale to inny temat.




  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Jakie są rodzaje ograniczeń dostępnych w SQL Server — SQL Server / samouczek T-SQL, część 50

  2. Nie można utworzyć wiersza o rozmiarze 8937, który jest większy niż dopuszczalne maksimum 8060

  3. jak wyświetlić cały raport na jednej stronie za pomocą Konstruktora raportów?

  4. Problem z zapytaniem sql podczas raportowania

  5. SQL Server — Utworzyć kopię tabeli bazy danych i umieścić ją w tej samej bazie danych?