Konvertierte eine Skalarfunktion in eine TVF-Funktion für die parallele Ausführung. Wird weiterhin im seriellen Modus ausgeführt


10

Eine meiner Abfragen in wurde nach einer Veröffentlichung im seriellen Ausführungsmodus ausgeführt, und ich stellte fest, dass zwei neue Funktionen in einer Ansicht verwendet wurden, auf die in der von der Anwendung generierten LINQ to SQL-Abfrage verwiesen wird. Also habe ich diese SCALAR-Funktionen in TVF-Funktionen konvertiert, aber die Abfrage wird immer noch im seriellen Modus ausgeführt.

Früher habe ich in einigen anderen Abfragen die Konvertierung von Scalar in TVF durchgeführt und das Problem der erzwungenen seriellen Ausführung gelöst.

Hier ist die Skalarfunktion:

CREATE FUNCTION [dbo].[FindEventReviewDueDate]
(
       @EventNumber VARCHAR(20),
       @EventID VARCHAR(25),
          @EventIDDate BIT
)

RETURNS DateTime
AS
BEGIN

DECLARE @CurrentEventStatus VARCHAR(20)
DECLARE @EventDateTime DateTime
DECLARE @ReviewDueDate DateTime


SELECT @CurrentEventStatus = (SELECT cis.EventStatus
                                 FROM CurrentEventStatus cis 
                                 INNER JOIN Event1 r WITH (NOLOCK) ON (cis.Event1Id = r.Id)
                                 WHERE (r.EventNumber = @EventNumber) AND r.EventID = @EventID)

SELECT @EventDateTime = (SELECT EventDateTime FROM Event1 r 
                          WHERE (r.EventNumber = @EventNumber) AND r.EventID = @EventID)

IF @CurrentEventStatus IN ('0','6') AND EventIDDate = 1
BEGIN

       SET @ReviewDueDate = DATEADD(DAY, 30, @EventDateTime)

       WHILE @ReviewDueDate < getdate() 
             SET @ReviewDueDate = DATEADD(DAY, 30, @ReviewDueDate)

       DECLARE @EventDateJournalDate DateTime

       SELECT @EventDateJournalDate = (SELECT TOP 1 ij.Date
                                       FROM EventPage_EventJournal ij 
                                       INNER JOIN EventJournalPages p ON ij.PageId = p.Id 
                                       INNER JOIN Journal f ON p.FormId = f.Id 
                                       INNER JOIN Event1 r WITH (NOLOCK) ON (f.Event1Id = r.Id)
                                       WHERE (r.EventNumber = @EventNumber AND r.EventID = @EventID) AND ij.ReviewType = 'Supervisor Monthly Review' ORDER BY ij.Date DESC)

      IF(DATEADD(DAY, 30, @EventDateTime) < getdate() AND
           (@EventDateJournalDate is null OR DATEADD(DAY, 30, @EventDateJournalDate) < getdate()) AND
              DATEADD(DAY, 14, @ReviewDueDate) > DATEADD(DAY, 30, getdate()))
                  SET @ReviewDueDate = DATEADD(DAY, -30, @ReviewDueDate)
         ELSE IF((@EventDateJournalDate is not null ) AND (DATEADD(DAY, 30, @EventDateJournalDate) >= @ReviewDueDate))
                  SET @ReviewDueDate = DATEADD(DAY, 30, @ReviewDueDate)

END
RETURN @ReviewDueDate

END

Hier ist die konvertierte TVF-Funktion.

CREATE FUNCTION [dbo].[FindEventReviewDueDate_test]
(
       @EventNumber VARCHAR(20),
       @EventID VARCHAR(25),
          @EventIDDate BIT
)

RETURNS @FunctionResultTableVairable TABLE (
 CurrentEventStatus varchar(20),
 Event1DateTime DateTime,
 ReviewDueDate DateTime
 )
AS 
BEGIN

DECLARE @CurrentEventStatus VARCHAR(20)
DECLARE @EventDateTime DateTime
DECLARE @ReviewDueDate DateTime


SELECT @CurrentEventStatus = (SELECT cis.EventStatus
                                 FROM CurrentEventStatus cis 
                                 INNER JOIN Event1 r WITH (NOLOCK) ON (cis.Event1Id = r.Id)
                                 WHERE (r.EventNumber = @EventNumber) AND r.EventID = @EventID)

SELECT @EventDateTime = (SELECT EventDateTime FROM Event1 r 
                          WHERE (r.EventNumber = @EventNumber) AND r.EventID = @EventID)

IF @CurrentEventStatus IN ('0','6') AND EventIDDate = 1
BEGIN

       SET @ReviewDueDate = DATEADD(DAY, 30, @EventDateTime)

       WHILE @ReviewDueDate < getdate() 
             SET @ReviewDueDate = DATEADD(DAY, 30, @ReviewDueDate)

       DECLARE @EventDateJournalDate DateTime

       SELECT @EventDateJournalDate = (SELECT TOP 1 ij.Date
                                       FROM EventPage_EventJournal ij 
                                       INNER JOIN EventJournalPages p ON ij.PageId = p.Id 
                                       INNER JOIN Journal f ON p.FormId = f.Id 
                                       INNER JOIN Event1 r WITH (NOLOCK) ON (f.Event1Id = r.Id)
                                       WHERE (r.EventNumber = @EventNumber AND r.EventID = @EventID) AND ij.ReviewType = 'Supervisor Monthly Review' ORDER BY ij.Date DESC)

      IF(DATEADD(DAY, 30, @EventDateTime) < getdate() AND
           (@EventDateJournalDate is null OR DATEADD(DAY, 30, @EventDateJournalDate) < getdate()) AND
              DATEADD(DAY, 14, @ReviewDueDate) > DATEADD(DAY, 30, getdate()))
                  SET @ReviewDueDate = DATEADD(DAY, -30, @ReviewDueDate)
         ELSE IF((@EventDateJournalDate is not null ) AND (DATEADD(DAY, 30, @EventDateJournalDate) >= @ReviewDueDate))
                  SET @ReviewDueDate = DATEADD(DAY, 30, @ReviewDueDate)
                   insert into @FunctionResultTableVairable
      select @CurrentEventStatus,@EventDateTime,@ReviewDueDate          

END
return;
END

GO

Stimmt etwas mit meiner Implementierung der TVF-Funktion nicht, die verhindert, dass die Abfrage im parallelen Modus ausgeführt wird?

Ich benutze die TVF-Funktion in der Abfrage wie folgt;

select ReviewDueDate from dbo.FunctionResultTableVairable('a','b','c')

Meine eigentliche Abfrage, die die Ansicht verwendet, ist ziemlich komplex, und wenn ich den Funktionsteil in der Ansicht und beim Ausführen auskommentiere, wird die Abfrage parallel ausgeführt. Es ist also die Funktion, die die Abfrage dazu zwingt, parallel ausgeführt zu werden.

Meine eigentliche Anfrage ist im folgenden Format.

select 
dv.column1,
dv.column2,
---------
---------
--------
(select ReviewDueDate from dbo.FunctionResultTableVairable('a','b','c')) AS 'Columnx'
from
DemoView dv
Where 
condition1
conditon 2

Jede Hilfe wird geschätzt.


3
Was sagt der Abfrageplan?
David Browne - Microsoft

2
Abgesehen davon, dass es einen großen Unterschied zwischen einer Inline-TVF und einer TVF mit mehreren Anweisungen gibt, wenn Ihre TVF für jede Zeile in der äußeren Abfrage dieselbe Zeile zurückgibt (da nur Konstanten verwendet werden) und Sie sich nur um eine Ausgabespalte kümmern , warum setzen Sie es in eine Unterabfrage in der Auswahlliste? Dies macht es einfach möglich, ohne Grund immer wieder auszuführen. Weisen Sie die Ausgabe einer Variablen zu und verwenden Sie die Variable in Ihrer Abfrage.
Aaron Bertrand

Antworten:


5

Kann ich meine Skalarfunktion in Inline-TVF konvertieren?

Ja. So etwas wie das Folgende würde es tun.

Es ist immer noch ziemlich heftig und wenn der Lauf korreliert wäre, wäre es wahrscheinlich ziemlich ineffizient. Wie Aaron in den Kommentaren hervorhebt, rufen Sie dies mit konstanten Werten auf. Hoffentlich spiegelt der Abfrageplan dies wider und führt ihn nur einmal aus.

CREATE FUNCTION [dbo].[FindEventReviewDueDateInline] (@EventNumber VARCHAR(20),
                                                      @EventID     VARCHAR(25),
                                                      @EventIDDate BIT)
RETURNS TABLE
AS
    RETURN
      WITH X
           AS (SELECT cis.EventStatus AS CurrentEventStatus,
                      r.EventDateTime
               FROM   CurrentEventStatus cis
                      INNER JOIN Event1 r
                              ON cis.Event1Id = r.Id
               WHERE  r.EventNumber = @EventNumber
                      AND r.EventID = @EventID
                      AND cis.EventStatus IN ( '0', '6' )
                      AND @EventIDDate = 1)
      SELECT X.CurrentEventStatus,
             X.EventDateTime,
             CA4.ReviewDueDate
      FROM   X
             --SET @ReviewDueDate = DATEADD(DAY, 30, @EventDateTime)
             CROSS APPLY(VALUES(DATEADD(DAY, 30, X.EventDateTime))) CA1(ReviewDueDate)
             -- WHILE @ReviewDueDate < getdate() 
             --       SET @ReviewDueDate = DATEADD(DAY, 30, @ReviewDueDate)
             CROSS APPLY(VALUES( IIF(CA1.ReviewDueDate >= GETDATE(), CA1.ReviewDueDate, DATEADD(DAY, 30 * CEILING(( IIF(CAST(GETDATE() AS TIME) > CAST(CA1.ReviewDueDate AS TIME), 1, 0)
                                                                                                           + DATEDIFF(DAY, CA1.ReviewDueDate, GETDATE()) ) / 30.0), CA1.ReviewDueDate)))) CA2(ReviewDueDate)
             --SELECT @EventDateJournalDate = ....
             CROSS APPLY(SELECT TOP 1 ij.Date
                         FROM   EventPage_EventJournal ij
                                INNER JOIN EventJournalPages p
                                        ON ij.PageId = p.Id
                                INNER JOIN Journal f
                                        ON p.FormId = f.Id
                                INNER JOIN Event1 r WITH (NOLOCK)
                                        ON ( f.Event1Id = r.Id )
                         WHERE  ( r.EventNumber = @EventNumber
                                  AND r.EventID = @EventID )
                                AND ij.ReviewType = 'Supervisor Monthly Review'
                         ORDER  BY ij.Date DESC) CA3(EventDateJournalDate)
             -- IF(DATEADD(DAY, 30, @EventDateTime) < getdate()
             CROSS APPLY(VALUES ( CASE
                          WHEN ( DATEADD(DAY, 30, X.EventDateTime) < GETDATE()
                                 AND ( CA3.EventDateJournalDate IS NULL
                                        OR DATEADD(DAY, 30, CA3.EventDateJournalDate) < GETDATE() )
                                 AND DATEADD(DAY, 14, CA2.ReviewDueDate) > DATEADD(DAY, 30, GETDATE()) )
                            THEN DATEADD(DAY, -30, CA2.ReviewDueDate)
                          WHEN( ( CA3.EventDateJournalDate IS NOT NULL )
                                AND ( DATEADD(DAY, 30, CA3.EventDateJournalDate) >= CA2.ReviewDueDate ) )
                            THEN DATEADD(DAY, 30, CA2.ReviewDueDate)
                          ELSE CA2.ReviewDueDate
                        END )) CA4(ReviewDueDate); 

11

Forrest hat größtenteils recht, aber die feineren Details sind:

SQL Server kann Änderungen an Tabellenvariablen, die Ihre Funktion verwendet, nicht parallelisieren.

Vor der Interleaved Execution von SQL Server 2017 waren die Zeilenschätzungen aus Funktionen mit Tabellenwerten für mehrere Anweisungen sehr niedrig.

Ein Nebeneffekt davon ist, dass Pläne im unteren Bereich sehr schlecht bewertet wurden und häufig die Kostenschwelle für Parallelität nicht überschreiten.


1
Okay, können Sie eine alternative Lösung vorschlagen, damit meine Hauptabfrage im parallelen Modus ausgeführt werden kann? Ist es bei Betrachtung meiner Skalarfunktion möglich, meine Skalarfunktion in Inline-TVF zu konvertieren?
user9516827

1
@ user9516827 Sie könnten wahrscheinlich einige CTEs miteinander verketten, um etwas Ähnliches zu tun, aber ich habe keine Ahnung, ob es parallel verlaufen, eine bessere Leistung erbringen usw. Es liegt an Ihnen, es auszuprobieren.
Erik Darling

@MartinSmith: Meine eigentliche Abfrage ist eine sehr komplexe Abfrage mit vielen Verknüpfungen und Verknüpfungen zu Ansichten usw. Diese Funktion wird für eine Auswahlspalte in der Hauptabfrage verwendet, und deshalb habe ich versucht, diese zu beheben. Auch dies ist ein LINQ -SQL (exec sp_executesql form) generierte Abfrage und ich habe die Einschränkung, den .net-Code nicht zu hacken. Danke
user9516827

10

SQL Server kann TVFs mit mehreren Anweisungen nicht parallelisieren. Es können nur Inline-TVFs parallelisiert werden.


1
Da ich dort Variablen habe, kann ich die Skalarfunktion nicht in Inline-TVF konvertieren?
user9516827
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.