2

首先,一些背景。

我们有一个订单处理系统,员工在应用程序中输入有关订单的计费数据,该应用程序将其存储在 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 年)以来的每个订单的数据,因此它们往往有点大。表中使用的OrderIDandEntryDate值在StaffEntry导入到大型机时不会保留,这就是连接稍微复杂一些的原因。最后,由于我要在MainFrame表中查找不存在的记录,所以在执行 JOIN 之后,我们IS NULL在 where 子句中有丑陋的东西。

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

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

4

7 回答 7

5

首先,您可以摆脱第二个 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 之后,我们在 where 子句中有丑陋的 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 检查?

于 2008-11-07T16:50:21.043 回答
1

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

于 2008-11-07T16:49:11.293 回答
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

if s.OrderID IS NULL, thenr.OrderID = s.OrderID永远不会为真,因此不会[Rejected]包含 from 的任何行,因此如给定的那样,它等效于:

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

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

于 2008-11-07T16:49:54.430 回答
1

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

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

祝你好运!

问候, 弗兰克

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

于 2008-11-07T16:56:04.087 回答
1

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

我还将查看执行计划,并对您的哪个JOINs 确实最昂贵进行简单的黑盒评估 -m或者r,仅使用一个或另一个对查询进行基准测试。我怀疑这是m因为多列和缺少有用的索引。

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

于 2008-11-07T17:01:57.273 回答
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....
于 2008-11-07T23:20:23.593 回答
0

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

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

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

于 2008-11-07T17:31:10.397 回答