加速sql JOIN

时间:2008-11-07 16:44:20

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

首先,一些背景知识。

我们有一个订单处理系统,员工在应用程序中输入有关订单的帐单数据,该应用程序将其存储在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年)开始保存每个订单的数据,因此它们往往有点大。导入到大型机时,OrderID表中使用的EntryDateStaffEntry值不会被保留,这就是为什么该连接稍微复杂一些。最后,因为我在MainFrame表中寻找不存在的记录,所以在JOIN之后我们在where子句中有那个丑陋的IS NULL

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

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

7 个答案:

答案 0 :(得分:5)

首先,你可以摆脱第二次LEFT JOIN。

无论如何,你的WHERE删除了任何匹配...例如,如果S.OrderID为1并且有一个值为1的R.OrderID,则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检查?

答案 1 :(得分:1)

在开始考虑更改查询之前,应确保所有表都具有对此查询和所有其他重要查询都有意义的聚簇索引。在表上拥有聚簇索引在sql server中至关重要,以确保正常运行。

答案 2 :(得分:1)

这没有意义:

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

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

答案 3 :(得分:1)

除了Kasperjj建议的内容(我同意应该是第一个),您可以考虑使用临时表来限制数据量。现在,我知道,我知道每个人都说远离温度表。而且通常做但有时候,值得尝试一下,因为你可以通过这种方法缩小数据量以大幅加入;这使整个查询更快。 (当然这取决于你可以缩减结果集的数量。)

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

祝你好运!

此致 弗兰克

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

答案 4 :(得分:1)

对所有表格进行索引非常重要。如果你对连接中使用的[MainFrame]列的索引做不了多少,你也可以在[MainFrame]中预先限制要搜索的行(和[Rejected],虽然看起来已经有了PK)通过指定日期范围 - 如果日期窗口应大致相似。这可以减少该连接的右侧。

我还会查看执行计划,并通过对基准测试,对您JOIN中哪些m确实最贵 - rm做一个简单的黑盒评估查询只有一个或另一个。我怀疑它是{{1}},因为有多列并且缺少有用的索引。

您可以在范围的几天或几个月内使用m.EntryDate。但是如果你已经在大型机上有索引,问题是为什么它们不被使用,或者如果它们被使用,为什么性能如此慢。

答案 5 :(得分:1)

尝试改变 LEFT JOIN [拒绝] r与(nolock)ON r.OrderID = s.OrderID 进入正确的合并:

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

答案 6 :(得分:0)

<强>更新
如果它不是很明显,我在原始问题的代码中犯了一个错误。现在已经修复了,但不幸的是,这意味着一些更好的回应实际上是朝着完全错误的方向发展。

我还有一些统计信息更新:通过严格限制StaffEntry.EntryDate使用的数据范围,我可以使查询运行得更好,更快捷。不幸的是,我只能做到这一点,因为一旦我知道了我关心的确切日期,经过很长一段路。我通常不会提前知道。

原始运行的执行计划显示StaffEntry表上的聚集索引扫描成本为78%,索引成本为MainFrame表的成本为11%,然后成本为0%在连接本身。使用较窄的日期范围运行它,对于StaffEntry的索引搜索,变为1%,对于'MainFrame'的索引搜索,变为1%,对于Rejected的表扫描,变为93%。这些是“实际”计划,而不是估计。