帮助改进SQL连接

时间:2011-02-09 14:50:35

标签: sql sql-server join indexing

我有一个存储过程,用于更新用户余额的游戏点。它是一个带有5个子查询的插入。我已将其中一个子查询隔离为查询,从而减慢整个批次的速度。没有它,存储过程将在2秒内运行。有了它,它将花费多达8秒。 8秒不是世界末日,但为了可扩展性,我需要更快地完成它。这是孤立的子查询:

(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子句中的一个函数会计算每个行 - 而不是以某种方式缓存函数的定义并以此方式进行评估。我确实有表上的索引,但我想知道其中一些是不正确的。

如果你不知道完整的数据库结构,我肯定很难确定问题所在,但我想指出一个方向,开始进一步孤立。

5 个答案:

答案 0 :(得分:2)

我怀疑你最大的性能命中来自相关子查询(无论表格在 U.UserId 之后)和嵌入式函数调用dbo.Event_CEAFKBID。当然,很大程度上取决于表的大小(正在读取多少行)。所有这些日期时间转换都无济于事,并产生非常强烈的“糟糕设计”气味,但我认为它们不会对性能产生太大影响。

那些左外连接是丑陋的,因为优化器必须将它们全部检查为行 - 所以如果“A”很大,所有连接所有行必须执行,即使那里没有数据。如果它们可以用内部连接替换,那么这样做,但我猜不是因为“表E 表G”逻辑。遗憾的是,它确实看起来像你已经得到的是三个单独的查询,一个问题;如果你把它分成三个,联合在一起,它看起来就像下面的Frankenstein查询。我不知道这是否会更快或更快(哎呀,我甚至无法调试查询并确保平面的平衡),但如果你有相对于你的逻辑的稀疏数据,这应该运行得非常快。 (我取出日期转换以使代码更清晰,你必须重新插入它们。)

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

答案 1 :(得分:1)

您可以将案例放在原因的位置,而不是直接选择第一行。 为什么你需要加入很多联接,如果在这个陈述中你只需要使用表A,E和G?

为了表现更好的查询,您可以在management Studio上使用执行计划。

答案 2 :(得分:1)

相关子查询是一种非常糟糕的编程技术,相当于在查询中使用游标。改为使其成为派生表。

是的,这些功能正在减慢你的速度。如果必须转换为datetime,则需要修复数据库结构并将数据正确存储为datetime。

答案 3 :(得分:1)

您是否需要在DATEDIFF功能的日期时间进行转换?您是将日期存储为测试,还是要重新转换以消除时间?如果你是,那么你就不需要像日子那样不正确,包括时间。

答案 4 :(得分: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可能是对外部查询的引用。