0

具有兩個UDT參數的查詢需要0.3秒,但是當封裝在內聯表值函數中時,需要3.5+秒。慢UDF的SQL替代

我讀過(Why is a UDF so much slower than a subquery?),但我正在努力修復/重寫。

每低於@ JasonALong的反饋,

的SELECT語句中0.3秒完成執行計劃:https://www.brentozar.com/pastetheplan/?id=HJnrqC53Z(注意SQL可在此頁)。

代碼功能是完成了這個鏈接下面和執行計劃粘貼3.5秒:https://www.brentozar.com/pastetheplan/?id=BJZbqR93b

SELECT 
SelectedContracts.MeasurableID, 
SelectedContracts.EntityID, 

EntityName, 
EntityAbbrev, 
EntityLogoURL, 
EntityHex1, 
EntityHex2, 
EntitySportID, 

MeasurableName, 
MeasurableOrganizationID, 
YearFilter, 
SeasonFilter, 
CategoryFilter, 
ResultFilter, 
Logo4Result, 
MeasurableSportID, 
MouseoverFooter, 
ContractRank4Org, 
ContractEndUTC, 

HighContractPrice4Period, 
HighTradeID, 
HighTradeUTC, 
HighTradeNumberOfContracts, 
HighTradeCurrency, 

LowContractPrice4Period, 
LowTradeID, 
LowTradeUTC, 
LowTradeNumberOfContracts, 
LowTradeCurrency, 

LastTradePrice, 
LastTradeID, 
LastTradeUTC, 
LastTradeNumberOfContracts, 
LastTradeCurrency, 

SecondLastTradePrice, 
SecondLastTradeID, 
SecondLastTradeUTC, 
SecondLastTradeNumberOfContracts, 
SecondLastTradeCurrency, 

ContractPrice4ChangeCalc, 
ContractID4ChangeCalc, 
ContractUTC4ChangeCalc, 
ContractsNumberTraded4ChangeCalc, 
ContractCurrency4ChangeCalc, 

HighestBidID, 
HighestBidMemberID, 
HighestBidPrice, 
HighestBidAvailableContracts, 
HighestBidCurrency, 

LowestAskID, 
LowestAskMemberID, 
LowestAskPrice, 
LowestAskAvailableContracts, 
LowestAskCurrency 


FROM 
(
    SELECT 
     dbo.Contracts.MeasurableID, 
     dbo.Contracts.EntityID 
    FROM 
     dbo.Contracts 
    WHERE 
     dbo.Contracts.MeasurableID IN ((2030),(2017),(2018),(2019),(2020),(2028),(2024),(2027),(2029),(2022),(4018),(4019),(4020),(4021)) 
    GROUP BY 
     dbo.Contracts.MeasurableID, 
     dbo.Contracts.EntityID 
) SelectedContracts 


INNER JOIN 
(
    SELECT 
     dbo.Entities.ID, 
     --dbo.Entities.OrganizationID, -- Get OrganizationID from Measurable since some Entities (European soccer teams) have multiple Orgs 
     dbo.Entities.EntityName, 
     dbo.Entities.EntityAbbrev, 
     dbo.Entities.logoURL AS EntityLogoURL, 
     dbo.Entities.Hex1 AS EntityHex1, 
     dbo.Entities.Hex2 AS EntityHex2, 
     dbo.Entities.SportID AS EntitySportID 
    FROM 
     dbo.Entities 
) SelectedEntities ON SelectedContracts.EntityID = SelectedEntities.ID 


INNER JOIN 
(
    SELECT 
     dbo.Measurables.ID AS MeasurableID, 
     dbo.Measurables.Name AS MeasurableName, 
     dbo.Measurables.OrganizationID AS MeasurableOrganizationID, 
     dbo.Measurables.[Year] AS YearFilter, 
     dbo.Measurables.Season AS SeasonFilter, 
     dbo.Measurables.Category AS CategoryFilter, 
     dbo.Measurables.Result AS ResultFilter, 
     dbo.Measurables.Logo4Result, 
     dbo.Measurables.SportID AS MeasurableSportID, 
     dbo.Measurables.MouseoverFooter, 
     dbo.Measurables.ContractRank4Org, 
     dbo.Measurables.EndUTC AS ContractEndUTC 
    FROM 
     dbo.Measurables 
) MEASURABLES_table ON SelectedContracts.MeasurableID = MEASURABLES_table.MeasurableID 


LEFT JOIN 
(
    SELECT 
     MeasurableID, 
     EntityID, 
     ContractPrice AS HighContractPrice4Period, 
     ID AS HighTradeID, 
     UTCMatched AS HighTradeUTC, 
     NumberOfContracts AS HighTradeNumberOfContracts, 
     CurrencyCode AS HighTradeCurrency 
    FROM 
       (
        SELECT 
         *, ROW_NUMBER() OVER (
          PARTITION BY MeasurableID, 
          EntityID 
         ORDER BY 
          ContractPrice DESC, 
          ID DESC 
         ) RowNumber -- ID DESC means most recent trade of ties 
        FROM 
         Contracts 
        WHERE 
         MeasurableID IN ((2030),(2017),(2018),(2019),(2020),(2028),(2024),(2027),(2029),(2022),(4018),(4019),(4020),(4021)) 
         AND dbo.Contracts.UTCmatched < DATEADD(DAY, -30, SYSDATETIME()) 
         AND (   CurrencyCode IN (('GBP'), ('CAD'), ('INR'), ('BRL'), ('MXN'), ('CHF'), ('RUB')) 
           ) 
       ) AS InnerSelect4HighTrade 

    WHERE 
     InnerSelect4HighTrade.RowNumber = 1 

) HighTrades ON SelectedContracts.MeasurableID = HighTrades.MeasurableID AND SelectedContracts.EntityID = HighTrades.EntityID 


LEFT JOIN 
(
    SELECT 
     MeasurableID, 
     EntityID, 
     ContractPrice AS LowContractPrice4Period, 
     ID AS LowTradeID, 
     UTCMatched AS LowTradeUTC, 
     NumberOfContracts AS LowTradeNumberOfContracts, 
     CurrencyCode AS LowTradeCurrency 
    FROM 
     (
      SELECT 
        *, ROW_NUMBER() OVER (
        PARTITION BY MeasurableID, 
        EntityID 
       ORDER BY 
        ContractPrice ASC, 
        ID DESC 
       ) RowNumber -- ID DESC means most recent trade of ties 
      FROM 
       Contracts 
      WHERE 
       MeasurableID IN ((2030),(2017),(2018),(2019),(2020),(2028),(2024),(2027),(2029),(2022),(4018),(4019),(4020),(4021)) 
       AND dbo.Contracts.UTCmatched < DATEADD(DAY, -30, SYSDATETIME()) 
       AND (   CurrencyCode IN (('GBP'), ('CAD'), ('INR'), ('BRL'), ('MXN'), ('CHF'), ('RUB')) 
         )   
     ) AS InnerSelect4LowTrade 

    WHERE  InnerSelect4LowTrade.RowNumber = 1 

) LowTrades ON SelectedContracts.MeasurableID = LowTrades.MeasurableID AND SelectedContracts.EntityID = LowTrades.EntityID 


LEFT JOIN 
(
    SELECT 
     MeasurableID, 
     EntityID, 
     ContractPrice AS LastTradePrice, 
     ID AS LastTradeID, 
     UTCMatched AS LastTradeUTC, 
     NumberOfContracts AS LastTradeNumberOfContracts, 
     CurrencyCode AS LastTradeCurrency 
    FROM 
     (
      SELECT 
       *, ROW_NUMBER() OVER (
        PARTITION BY MeasurableID, 
        EntityID 
       ORDER BY 
        ID DESC 
       ) RowNumber -- ID DESC means most recent trade of ties 
      FROM 
       Contracts 
      WHERE 
       MeasurableID IN ((2030),(2017),(2018),(2019),(2020),(2028),(2024),(2027),(2029),(2022),(4018),(4019),(4020),(4021)) 
       AND (   CurrencyCode IN (('GBP'), ('CAD'), ('INR'), ('BRL'), ('MXN'), ('CHF'), ('RUB')) 
         ) 
     ) AS InnerSelect4LastTrade 

    WHERE InnerSelect4LastTrade.RowNumber = 1 

) LastTrades ON SelectedContracts.MeasurableID = LastTrades.MeasurableID AND SelectedContracts.EntityID = LastTrades.EntityID 


LEFT JOIN 
(
    SELECT 
     MeasurableID, 
     EntityID, 
     ContractPrice AS SecondLastTradePrice, 
     ID AS SecondLastTradeID, 
     UTCMatched AS SecondLastTradeUTC, 
     NumberOfContracts AS SecondLastTradeNumberOfContracts, 
     CurrencyCode AS SecondLastTradeCurrency 
    FROM 
     (
      SELECT 
       *, ROW_NUMBER() OVER (
        PARTITION BY MeasurableID, 
        EntityID 
       ORDER BY 
        ID DESC 
       ) RowNumber -- ID DESC means most recent trade of ties 
      FROM 
       Contracts 
      WHERE 
       MeasurableID IN ((2030),(2017),(2018),(2019),(2020),(2028),(2024),(2027),(2029),(2022),(4018),(4019),(4020),(4021)) 
       AND (   CurrencyCode IN (('GBP'), ('CAD'), ('INR'), ('BRL'), ('MXN'), ('CHF'), ('RUB')) 
         ) 
--need time filter??? 
     ) AS InnerSelect4SecondToLastTrade 

    WHERE InnerSelect4SecondToLastTrade.RowNumber = 2 

) SecondToLastTrade ON SelectedContracts.MeasurableID = SecondToLastTrade.MeasurableID AND SelectedContracts.EntityID = SecondToLastTrade.EntityID 


LEFT JOIN 
(
    SELECT 
     MeasurableID, 
     EntityID, 
     ContractPrice AS ContractPrice4ChangeCalc, 
     ID AS ContractID4ChangeCalc, 
     UTCMatched AS ContractUTC4ChangeCalc, 
     NumberOfContracts AS ContractsNumberTraded4ChangeCalc, 
     CurrencyCode AS ContractCurrency4ChangeCalc 
    FROM 
     (
      SELECT 
       *, ROW_NUMBER() OVER (
        PARTITION BY MeasurableID, 
        EntityID 
       ORDER BY 
        ID DESC -- ID DESC equals the most recent trade if ties 
       ) RowNumber 
      FROM 
       Contracts 
      WHERE 
       MeasurableID IN ((2030),(2017),(2018),(2019),(2020),(2028),(2024),(2027),(2029),(2022),(4018),(4019),(4020),(4021)) 
       AND (   CurrencyCode IN (('GBP'), ('CAD'), ('INR'), ('BRL'), ('MXN'), ('CHF'), ('RUB')) 
         ) 
      AND dbo.Contracts.UTCmatched < DATEADD(Day ,-30, SYSDATETIME()) 
     ) AS InnerSelect4ChangeCalcPerPeriod 

    WHERE InnerSelect4ChangeCalcPerPeriod.RowNumber = 1 

) Trade4ChangeCalcPerPeriod ON SelectedContracts.MeasurableID = Trade4ChangeCalcPerPeriod.MeasurableID AND SelectedContracts.EntityID = Trade4ChangeCalcPerPeriod.EntityID 


LEFT JOIN 
(
    SELECT 
     MeasurableID, 
     EntityID, 
     ID AS HighestBidID, 
     MemberID AS HighestBidMemberID, 
     BidPrice AS HighestBidPrice, 
     AvailableContracts AS HighestBidAvailableContracts, 
     CurrencyCode AS HighestBidCurrency 
    FROM 
     (
      SELECT 
       *, ROW_NUMBER() OVER (
        PARTITION BY MeasurableID, 
        EntityID 
       ORDER BY 
        BidPrice DESC, 
        ID DESC 
       ) RowNumber 
      FROM 
       dbo.Interest2Buy 
      WHERE 
       MeasurableID IN ((2030),(2017),(2018),(2019),(2020),(2028),(2024),(2027),(2029),(2022),(4018),(4019),(4020),(4021)) 
      AND AvailableContracts > 0 
       AND (   CurrencyCode IN (('GBP'), ('CAD'), ('INR'), ('BRL'), ('MXN'), ('CHF'), ('RUB')) 
         ) 
     ) AS InnerSelect4HighestBid 

    WHERE InnerSelect4HighestBid.RowNumber = 1 

) HighestBids ON SelectedContracts.MeasurableID = HighestBids.MeasurableID AND SelectedContracts.EntityID = HighestBids.EntityID 


LEFT JOIN 
(
    SELECT 
     MeasurableID, 
     EntityID, 
     ID AS LowestAskID, 
     MemberID AS LowestAskMemberID, 
     AskPrice AS LowestAskPrice, 
     AvailableContracts AS LowestAskAvailableContracts, 
     CurrencyCode AS LowestAskCurrency 
    FROM 
     (
      SELECT 
       *, ROW_NUMBER() OVER (
        PARTITION BY MeasurableID, 
        EntityID 
       ORDER BY 
        AskPrice ASC, 
        ID DESC 
       ) RowNumber 
      FROM 
       dbo.Interest2Sell 
      WHERE 
       MeasurableID IN ((2030),(2017),(2018),(2019),(2020),(2028),(2024),(2027),(2029),(2022),(4018),(4019),(4020),(4021)) 
       AND AvailableContracts > 0 
       AND (   CurrencyCode IN (('GBP'), ('CAD'), ('INR'), ('BRL'), ('MXN'), ('CHF'), ('RUB')) 
         ) 
     ) AS InnerSelect4BestAsk 

    WHERE InnerSelect4BestAsk.RowNumber = 1 

) BestAsks ON SelectedContracts.MeasurableID = BestAsks.MeasurableID AND SelectedContracts.EntityID = BestAsks.EntityID 
+2

這太含糊不清,你的UDF可能是標量函數或表值,它們可能是單語句或多語句,你可以將它們用作相關的子查詢或加入它們。該名單繼續下去。你需要給出一個與你的特定情況相關的實際例子,也許在閱讀完之後? https://stackoverflow.com/help/mcve *(爲兩個版本的代碼獲取執行計劃以查看它們的不同之處也不失爲一個好主意,這可能有助於找出它們之間的差異) * – MatBailie

+0

對於沿着fyi的任何讀者,我嘗試添加Option(重新編譯)https:// stackoverflow。com/questions/20864934/option-recompile-always-always-faster-why,但這沒有什麼區別。還試圖在.Net(web服務器)中創建SQL查詢並直接運行,但事實證明這比使用函數或存儲過程要慢。 –

+1

如果使用UDT運行它,但未包含在函數中會發生什麼?這將幫助您隔離導致性能問題的更改(添加UDT或封裝在函數中)。我懷疑那是UDT是這個問題。如果是這樣,請嘗試重寫查詢以加入遊戲而不是使用IN(),或者將索引和統計信息應用於UDT。 – MatBailie

回答

0

使用聯接,而不是「IN」的條款了大量幫助。 (儘管我也將表var改爲臨時表,並且這對錶格也有很大的幫助)

2

標量函數和木里語句表值函數(mTVF)的,因爲在你的問題中提到,「黑匣子」給優化器...

所以,我想問的是,「爲什麼這麼糟糕?」。答案是,爲了提出一個儘可能高效的好計劃,它需要知道有關特定需求的某些細節以及它將從中提取數據的表的信息(這就是爲什麼過時的靜態數據可以嚴重影響性能)。所以...當你使用標量函數或mTVF時,優化器無法像使用內聯代碼一樣評估所有需求。它的迴應是簡單地假設該函數只執行一次,並根據該假設制定計劃。

由於假設是錯誤的,錯誤的計劃會生成並最終導致可怕的表現。

解決方案是重寫有問題的函數......關鍵是要#1,確保將它們重寫爲「內聯表值函數」(iTVF)。這些是優化器將看到的唯一函數,就好像它們的代碼直接輸入到外部查詢中一樣(因此術語「內聯」)。如果你對iTVFs不熟悉,他們有2個要求...... 1他們必須是表格功能(無論什麼原因,MS STILL沒有可用的標量版本)......並且...... 2這是biggie ...函數體必須是單個語句。

那麼,如果你不需要一個表值函數,你需要一個標量函數?那麼沒有什麼說多值函數不能返回單個(標量)值......這就是爲什麼那些知道所有函數的代碼都是iTVF的情況的原因。

好的一點是,網絡上不缺少關於創建「內聯標量函數」的信息,使用表函數進行編碼以返回Web上的標量值。

希望這有助於...

+0

@Jasonalong謝謝,你的信息讓我指向更好的研究。然而,就你的評論的底部而言,我的問題是我想返回一個表格而不是標量。 (此外,我不認爲有可能將沒有JOINS的數據返回到子查詢中......) –

+1

很難說沒有任何細節......但是......就這樣就沒有混淆......當我說, 「作爲一個單一的陳述」,這不應該被解釋爲「簡單陳述」...... CTE,派生表和子查詢都完全可以接受。它只是意味着你不能使用IF塊來添加控制流或者聲明和設置內部變量。這裏有一個簡單的方法來思考它...... SQL語句應該用分號終止......如果你能在主體中添加多個分號,SQL Server會認爲它是多值的。 –

+1

如果想要驗證SQL Server是否將「內聯」函數視爲特定函數,只需查詢sys.objects表。 SELECT * FROM sys.objects o WHERE o.name = N'tfn_SomeFunction'; ...或者...你可以一次查看所有的uds ... SELECT * FROM sys.objects o WHERE o.type IN('FN','FS','IF','TF'); –