【问题标题】:Speed up sql JOIN加快sql JOIN
【发布时间】:2010-09-21 07:43:19
【问题描述】:

首先,一些背景。

我们有一个订单处理系统,工作人员在该系统中将有关订单的计费数据输入应用程序中,该应用程序将其存储在 sql server 2000 数据库中。这个数据库不是真正的计费系统:它只是一个保存位置,以便记录可以通过夜间批处理运行到大型机系统中。

此批处理是由外部供应商提供的罐装第三方包。它应该做的部分工作是为任何被拒绝的记录提供报告。拒绝报告是手动处理的。

不幸的是,第三方软件并没有捕捉到所有的错误。我们有单独的进程将数据从大型机拉回数据库中的另一个表中,并将拒绝的费用加载到另一个表中。

然后会运行审核流程,以确保员工最初输入的所有内容都可以在某个地方进行说明。该审计采用我们运行的 sql 查询的形式,它看起来像这样:

SELECT *
FROM [StaffEntry] s with (nolock)
LEFT JOIN [MainFrame] m with (nolock)
    ON m.ItemNumber = s.ItemNumber 
        AND m.Customer=s.Customer 
        AND m.CustomerPO = s.CustomerPO -- purchase order
        AND m.CustPORev = s.CustPORev  -- PO revision number
LEFT JOIN [Rejected] r with (nolock) ON r.OrderID = s.OrderID
WHERE s.EntryDate BETWEEN @StartDate AND @EndDate
    AND r.OrderID IS NULL AND m.MainFrameOrderID IS NULL

当然,这是经过大量修改的,但我相信重要的部分都得到了体现。问题是这个查询开始需要很长时间才能运行,我正在尝试找出如何加快它的速度。

我很确定问题是从 StaffEntry 表到 MainFrame 表的 JOIN。由于两者都保存了自一开始(本系统中为 2003 年)以来的每个订单的数据,因此它们往往有点大。 StaffEntry 表中使用的OrderIDEntryDate 值在导入到大型机时不会保留,这就是连接稍微复杂一些的原因。最后,由于我要在 MainFrame 表中查找不存在的记录,因此在执行 JOIN 后,我们在 where 子句中有那个丑陋的 IS NULL

StaffEntry 表由 EntryDate(集群)索引,并在 Customer/PO/rev 上单独索引。 MainFrame 由客户和主机费用编号(集群,其他系统需要)和客户/PO/Rev 单独索引。 Rejected 根本没有被索引,但它很小,测试表明这不是问题。

所以,我想知道是否有另一种(希望更快)方式可以表达这种关系?

【问题讨论】:

  • 你的表有什么索引?
  • 能否提供查询的执行计划?
  • 现在包含索引,但执行计划无法正常工作,因为它与我发布的查询的模糊版本不匹配。

标签: sql sql-server performance join sql-server-2000


【解决方案1】:

尝试改变 LEFT JOIN [Rejected] r with (nolock) ON r.OrderID = s.OrderID 进入 RIGHT MERGE JOIN:

SELECT ...
FROM [Rejected] r
     RIGHT MERGE JOIN [StaffEntry] s with (nolock) ON r.OrderID = s.OrderID
     LEFT JOIN [MainFrame] m with (nolock) ON....

【讨论】:

  • 这有点道理:从较小的表开始,然后首先添加具有匹配索引的表。然后稍后加入“混乱”表。它使结果集更小、更长,这是我在这里尝试宣扬的。
【解决方案2】:

更新:
如果还不是很明显,我在原始问题的代码中犯了一个错误。现在已经解决了,但不幸的是,这意味着这里的一些更好的响应实际上是在朝着完全错误的方向发展。

我还更新了一些统计信息:通过严格限制StaffEntry.EntryDate 使用的数据范围,我可以使查询运行得又快又好。不幸的是,我之所以能做到这一点,是因为在运行了很长时间之后,我才确切地知道我关心哪些日期。我通常不会提前知道。

最初运行的执行计划显示,StaffEntry 表的聚集索引扫描成本为 78%,MainFrame 表的索引查找成本为 11%,然后连接本身的成本为 0% .使用狭窄的日期范围运行它,对于StaffEntry 的索引查找更改为 1%,对于“MainFrame”的索引查找更改为 1%,对于Rejected 的表扫描更改为 93%。这些是“实际”计划,不是估计的。

【讨论】:

  • 如果原始代码有缺陷,您可以考虑关闭此代码,然后——如果问题仍然需要解决——使用正确的代码打开一个新代码 :) 并包含任何相关信息人们可能需要帮助你。祝你好运!
【解决方案3】:

对所有表进行索引将很重要。如果您无法对连接中使用的 [MainFrame] 列进行索引,您还可以预先限制要在 [MainFrame] (和 [Rejected] 中搜索的行,尽管它看起来已经有PK)通过指定日期范围 - 如果日期窗口应该大致相似。这可以减少该连接的右侧。

我还会查看执行计划,并通过仅使用一个或其他。我怀疑它是m,因为它有多个列并且缺少有用的索引。

您可以在您的范围内的几天或几个月内使用 m.EntryDate。但是如果您已经在 Mainframe 上建立了索引,那么问题是为什么不使用它们,或者如果它们正在使用,为什么性能这么慢。

【讨论】:

  • M 由 customer/po/rev 索引,赞成,因为限制大型机中的行是个好主意。我只是不确定如何实现它,因为日期确实只是“大致”相关,而不是完全相关。
【解决方案4】:

首先,您可以摆脱第二个 LEFT JOIN。

无论如何,您的 WHERE 正在删除任何匹配项...例如,如果 S.OrderID 为 1 并且 R.OrderID 的值为 1,则 WHERE 中的 IS NULL 强制执行将不允许它。所以它只会返回 s.OrderID 为 NULL 的记录,如果我没看错的话......

其次,如果您要处理大量数据,添加 NOLOCK 表提示通常不会有任何影响。假设您不介意在这里或那里进行脏读的可能性:-P 通常值得冒险。

SELECT *
FROM [StaffEntry] s (nolock)
LEFT JOIN [MainFrame] m (nolock) ON m.ItemNumber = s.ItemNumber 
    AND m.Customer=s.Customer 
    AND m.CustomerPO = s.CustomerPO -- purchase order
    AND m.CustPORev = s.CustPORev  -- PO revision number
WHERE s.EntryDate BETWEEN @StartDate AND @EndDate
    AND s.OrderID IS NULL

最后,你的问题有一部分对我来说不太清楚......

“因为我正在寻找 MainFrame 表中的记录 不存在,在做 JOIN 之后我们 在哪里有那个丑陋的 IS NULL 子句。”

好的...但是您是否试图将其限制在那些 MainFrame 表记录不存在的地方?如果是这样,您也会希望在 WHERE 中表达它,对吗?所以像这样......

SELECT *
FROM [StaffEntry] s (nolock)
LEFT JOIN [MainFrame] m (nolock) ON m.ItemNumber = s.ItemNumber 
    AND m.Customer=s.Customer 
    AND m.CustomerPO = s.CustomerPO -- purchase order
    AND m.CustPORev = s.CustPORev  -- PO revision number
WHERE s.EntryDate BETWEEN @StartDate AND @EndDate
    AND s.OrderID IS NULL AND m.ItemNumber IS NULL

如果这就是您对原始语句的意图,也许您可​​以摆脱 s.OrderID IS NULL 检查?

【讨论】:

  • 原代码确实没有锁,我一开始发的有一个错误,现在已经修复了。
【解决方案5】:

除了 Kasperjj 的建议(我同意应该首先提出)之外,您还可以考虑使用临时表来限制数据量。现在,我知道,我知道每个人都说要远离临时表。我通常会这样做,但有时值得一试,因为您可以使用这种方法大幅减少数据量;这使得整体查询更快。 (当然这取决于你可以缩小多少结果集。)

我最后的想法是,有时您只需要尝试不同的方法来组合查询。这里的任何人都可能有太多变量无法给出答案....另一方面,这里的人很聪明,所以我可能是错的。

祝你好运!

问候, 弗兰克

PS:我忘了提到,如果你想尝试这种临时表方法,你还需要在临时表上试验不同的索引和主键。根据数据量,索引和 PK 会有所帮助。

【讨论】:

    【解决方案6】:

    这没有意义:

    SELECT *
    FROM [StaffEntry] s
    LEFT JOIN [MainFrame] m ON m.ItemNumber = s.ItemNumber 
        AND m.Customer=s.Customer 
        AND m.CustomerPO = s.CustomerPO -- purchase order
        AND m.CustPORev = s.CustPORev  -- PO revision number
    LEFT JOIN [Rejected] r ON r.OrderID = s.OrderID
    WHERE s.EntryDate BETWEEN @StartDate AND @EndDate
        AND r.OrderID IS NULL AND s.OrderID IS NULL
    

    如果s.OrderID IS NULL,那么r.OrderID = s.OrderID 将永远不会为真,因此不会包含来自[Rejected] 的任何行,因此给定,它相当于:

    SELECT *
    FROM [StaffEntry] s
    LEFT JOIN [MainFrame] m ON m.ItemNumber = s.ItemNumber 
        AND m.Customer=s.Customer 
        AND m.CustomerPO = s.CustomerPO -- purchase order
        AND m.CustPORev = s.CustPORev  -- PO revision number
    WHERE s.EntryDate BETWEEN @StartDate AND @EndDate
        AND s.OrderID IS NULL
    

    您确定您发布的代码是正确的吗?

    【讨论】:

      【解决方案7】:

      在您开始考虑更改查询之前,您应该确保所有表都有一个对这个查询和所有其他重要查询都有意义的聚集索引。在你的表上拥有聚集索引对于确保适当的性能在 sql server 中至关重要。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2010-12-21
        • 2011-09-06
        • 2011-05-06
        • 2018-07-10
        • 1970-01-01
        • 2016-10-14
        • 1970-01-01
        相关资源
        最近更新 更多