2011-02-09 47 views
0

我有一個存儲過程運行以更新用戶餘額的遊戲點。這是一個插入5個子查詢。我已經隔離了其中一個子查詢作爲減慢整個批次的查詢。沒有它,存儲過程將在2秒內運行。有了它,它將需要多達8秒。 8秒不是世界末日,但爲了可擴展性,我需要更快完成。這裏是隔離的子查詢:幫助改進SQL加入

(SELECT IsNull(Sum(A.TransAmount) + Sum(Case When A.BetResult = 1 Then (A.BetWinAmount + (A.TransAmount * -1)) End), 0) 
      FROM User_T A 
      LEFT OUTER JOIN User_TD B on A.TID = B.TID 
      LEFT OUTER JOIN Lines_BL C ON B.LID = C.LID 
      LEFT OUTER JOIN Lines_BM D ON C.BMID = D.BMID 
      LEFT OUTER JOIN Event_M E ON D.EID = E.EID 
      LEFT OUTER JOIN Event_KB F ON A.TransReason = F.BID 
      LEFT OUTER JOIN Event_M G ON F.BID = G.EID 
     where A.UserID = U.UserID AND (A.IsSettled = 1) 
     AND 
     (
     (A.TransReason = 1 AND (datediff(dd, Convert(datetime, E.EDate, 101), Convert(datetime, @EndDate, 101)) = @DaysAgo)) OR 
     (A.TransReason >= 3000 AND (datediff(dd, Convert(datetime, G.EDate, 101), Convert(datetime, @EndDate, 101)) = @DaysAgo) 
       AND [dbo].[Event_CEAFKBID](A.TransReason) = 1) OR 
     (A.TransReason BETWEEN 3 and 150 AND (datediff(dd, Convert(datetime, A.TransDT, 101), Convert(datetime, @EndDate, 101)) = @DaysAgo)) 
     ) 

我做了什麼,以進一步孤立:當我剛剛加入運行SELECT *(沒有where子句),性能非常不錯 - > 100000行的下一個第二。正如我在where子句中所加的那樣,我認爲減慢的好處來自'or'子句和/或需要評估的函數。

據我所知,where子句中的函數計算每行行 - 而不是以某種方式緩存函數的定義並評估該方式。我在桌子上有索引,但我想知道他們中有些是不正確的。如果沒有你知道完整的數據庫結構,我相信很難找出問題所在,但我想指出一個方向開始進一步隔離。

+3

您是否看過查詢的執行計劃?這通常會幫助你指出正確的方向。特別是在預期不能使用痕跡的情況下,或者哪些連接是最昂貴的情況下。 –

+0

是的,這是一項功能...什麼是淨化? – IMAbev

回答

2

我懷疑你最大的性能匹配來自相關子查詢(無論表是背後U.UserId)和嵌入式功能調用dbo.Event_CEAFKBID。很大程度上取決於表格的大小(正在讀取多少行)。所有這些日期時間轉換都無濟於事,併產生非常強烈的「糟糕設計」氣味,但我認爲他們不會對性能造成太大影響。

那些左外連接是醜陋的,因爲優化必須檢查他們所有行 - 所以,如果「A」是大,所有加入所有該行已被執行,即使沒有數據在那裏。如果它們可以用內連接代替,那麼這樣做,但我猜不是因爲「表格E 表G」邏輯。減少,它確實看起來像你有三個單獨的查詢一個一個;如果你把它分成三份,聯合在一起,它會看起來像下面的弗蘭肯斯坦查詢。我不知道這是否會運行得更快(哎呀,我甚至無法調試查詢並確保平衡點),但是如果您的邏輯相對於您的邏輯來說數據稀疏,那麼運行速度會非常快。 (我拿出日期轉換以使代碼更清晰,您必須重新插入。)

SELECT isnull(sum(Total), 0) FinalTotal from (
SELECT 
    sum(A.TransAmount + Case When A.BetResult = 1 Then A.BetWinAmount - A.TransAmount else 0 End) Total 
FROM User_T A    
INNER JOIN User_TD B on A.TID = B.TID    
INNER JOIN Lines_BL C ON B.LID = C.LID    
INNER JOIN Lines_BM D ON C.BMID = D.BMID    
INNER JOIN Event_M E ON D.EID = E.EID    
where A.UserID = U.UserID 
    AND A.IsSettled = 1 
    AND A.TransReason = 1 
    AND (datediff(dd, E.EDate, @EndDate) = @DaysAgo)) 

UNION ALL SELECT 
    sum(A.TransAmount + Case When A.BetResult = 1 Then A.BetWinAmount - A.TransAmount else 0 End) Total 
FROM User_T A    
INNER JOIN Event_KB F ON A.TransReason = F.BID    
INNER JOIN Event_M G ON F.BID = G.EID   
where A.UserID = U.UserID 
    AND A.IsSettled = 1 
    AND A.TransReason >= 3000 
    AND (datediff(dd, G.EDate, @EndDate) = @DaysAgo)     
    AND [dbo].[Event_CEAFKBID](A.TransReason) = 1 

UNION ALL SELECT 
    sum(A.TransAmount + Case When A.BetResult = 1 Then A.BetWinAmount - A.TransAmount else 0 End) Total 
FROM User_T A    
where A.UserID = U.UserID 
    AND A.IsSettled = 1 
    AND A.TransReason BETWEEN 3 and 150 
    AND datediff(dd, A.TransDT, @EndDate) = @DaysAgo) 
) ThreeWayUnion 
+0

太棒了,菲利普!很多食物的思想。我將嘗試運行這個,看看會發生什麼。 – IMAbev

+0

做大量的測試。請注意UNION和UNION ALL之間的關鍵區別,因爲您現在將運行三個相關的子查詢而不是一個,所以它可能會更慢... –

+0

Phil(或任何人)我正在重新閱讀並想知道日期時間的替代方案轉換。如果我在第二行發生時寫行,那麼如何確定行在特定日期的時間? – IMAbev

1

您可以將案件放在何處,而不是直接在選擇第一行。 爲什麼你需要把許多連接,如果在這個名單中,你只是使用表A,E和G?

爲了提高性能,您可以在Management Studio上使用執行計劃。

+0

獲取表A,E和G的唯一方法是通過聯接中的其他表。我已經使用了執行計劃,但解讀它將是一個不同的問題。 – IMAbev

1

相關子查詢是一種很差的編程技術,它等同於在查詢中使用遊標。改爲派生表。

是的,這些功能正在放慢你的速度。如果必須轉換爲日期時間,則需要修正數據庫結構,並將數據正確存儲爲日期時間。

+0

優秀信息,謝謝!我會查找派生表,我相信我還有很多其他存儲過程可以清理。 – IMAbev

+0

您也可以使用CTE而不是派生表。 – HLGEM

1

您是否需要在DATEDIFF函數的日期時間進行轉換?你是否將日期存儲爲測試,或者你是否正在恢復以擺脫時間?如果你是,那麼你不需要包括時間在內的天數不同。

+0

偉大的問題 - 真的讓我想到......我正在恢復,以擺脫時間。由於發生的不同事件(插入的行),它們具有實際的時間戳,但我實際上需要整天的活動。不過,我想我仍然需要重新轉變。 – IMAbev

+0

正如我上面所述,我刪除了日期轉換,只看到非常小的性能增益。經過進一步的審查,我的轉換是多餘的,因爲轉換器的數據已經是日期時間數據類型。也許這就是爲什麼不會有微不足道的表現收益? – IMAbev

1

您應該檢查外部連接是否必要 - 它們比內部連接更昂貴。你有一些來自主導表的標記爲A的值。你還有一個引用E的OR條件和一個引用G的OR條件。我希望沿着以下幾行重構查詢:

SELECT SUM(x.result) 
    FROM (SELECT A.TransAmount + CASE WHEN A.BetResult = 1 
           THEN (A.BetWinAmount + (A.TransAmount * -1)) 
           ELSE 0 END AS result 
      FROM A 
     WHERE A.TransReason BETWEEN 3 AND 150 
      AND datediff(dd, Convert(datetime, A.TransDT, 101), 
          Convert(datetime, @EndDate, 101)) = @DaysAgo 
      AND A.UserID = U.UserID -- Where does alias U come from? 
      AND A.IsSettled = 1 
     UNION 
     SELECT A.TransAmount + CASE WHEN A.BetResult = 1 
           THEN (A.BetWinAmount + (A.TransAmount * -1)) 
           ELSE 0 END AS result 
      FROM User_T A 
      JOIN User_TD B ON A.TID = B.TID 
      JOIN Lines_BL C ON B.LID = C.LID 
      JOIN Lines_BM D ON C.BMID = D.BMID 
      JOIN Event_M E ON D.EID = E.EID 
     WHERE A.TransReason = 1 
      AND datediff(dd, Convert(datetime, E.EDate, 101), 
          Convert(datetime, @EndDate, 101)) = @DaysAgo 
      AND A.UserID = U.UserID -- Where does alias U come from? 
      AND A.IsSettled = 1 
     UNION 
     SELECT A.TransAmount + CASE WHEN A.BetResult = 1 
           THEN (A.BetWinAmount + (A.TransAmount * -1)) 
           ELSE 0 END S result 
      FROM User_T A 
      JOIN User_TD B ON A.TID = B.TID 
      JOIN Lines_BL C ON B.LID = C.LID 
      JOIN Lines_BM D ON C.BMID = D.BMID 
      JOIN Event_M E ON D.EID = E.EID 
      JOIN Event_KB F ON A.TransReason = F.BID 
      JOIN Event_M G ON F.BID = G.EID 
     WHERE A.TransReason >= 3000 
      AND datediff(dd, Convert(datetime, G.EDate, 101), 
          Convert(datetime, @EndDate, 101)) = @DaysAgo 
      AND [dbo].[Event_CEAFKBID](A.TransReason) = 1 
      AND A.UserID = U.UserID -- Where does alias U come from? 
      AND A.IsSettled = 1 
     ) AS x 

這裏的想法是,內部連接查詢將比外部連接查詢更快,並且彙總中間結果對於DBMS來說並不困難(它總是這樣做)。它可能也避免了需要IFNULL

別名U大概是對這是其中一部分的外部查詢的引用。

+0

嗨喬納森。偉大的東西! U表是用戶表。隨着你的迴應和菲利普的迴應,看起來我真的需要在聯盟查詢中找到我的頭。 – IMAbev

+0

喬納森 - 我終於有時間坐下來理解這一點。在你的例子的幫助下,我能夠重寫子查詢的這一部分。我通過分析器運行了新舊腳本,我從> 21,000次讀取和250ms到1318次讀取和62ms!希望當我把所有這一切扔在一起,我看到巨大的性能收益!僅供參考一些建議刪除日期轉換。我測試了無論和沒有提供約5毫秒的增益。 – IMAbev