【问题标题】:Can this SQL Query be optimized to run faster?可以优化此 SQL 查询以更快地运行吗?
【发布时间】:2012-06-27 02:21:27
【问题描述】:

我有一个需要很长时间才能完成的 SQL 查询(对于 SQL Server 2008 R2)。我想知道是否有更好的方法?

SELECT @count = COUNT(Name)
FROM Table1 t
WHERE t.Name = @name AND t.Code NOT IN (SELECT Code FROM ExcludedCodes)

Table1 中有大约 9000 万行,并按名称和代码进行索引。 ExcludedCodes 中只有大约 30 行。

这个查询在一个存储过程中,被调用了大约 40k 次,完成该过程的总时间是 27 分钟。我相信这是我最大的瓶颈,因为它查询的行数量很大,而且它执行的次数。

因此,如果您知道优化它的好方法,将不胜感激!如果它不能被优化,那么我想我会坚持 27 分钟......

编辑

我将 NOT IN 更改为 NOT EXISTS 并将时间缩短到 10:59,因此仅此一项对我来说是一个巨大的收获。我仍然会尝试按照下面的建议执行 group by 语句,但这需要完全重写存储过程并且可能需要一些时间......(正如我之前所说,我不是最擅长 SQL,但它正在开始在我身上成长。^^)

【问题讨论】:

  • 伙计,你正在解析 90M-30 行 40000 次......你到底期望什么:D
  • 你考虑过使用 JOIN 吗?
  • lol 我没想到它会很快,但我不是 SQL 库鲁,所以我不确定我是否遗漏了一些非常简单的东西。不,不幸的是,名称不是唯一的.. :(
  • 不,我并没有真正考虑过在这种情况下使用连接,真的不确定它是否会更快/老实说是必要的。但就像我说的,我不是大师!大声笑
  • ExcludedCodes 上有索引吗?

标签: sql sql-server-2008 tsql optimization


【解决方案1】:

除了让查询本身响应更快的变通方法之外,您是否考虑过在表中维护一个列来告知它是否在此集合中?它需要大量维护,但如果ExcludedCodes 表不经常更改,那么进行维护可能会更好。例如,您可以添加一个 BIT 列:

ALTER TABLE dbo.Table1 ADD IsExcluded BIT;

将其设为 NOT NULL 并默认为 0。然后您可以创建过滤索引:

CREATE INDEX n ON dbo.Table1(name)
  WHERE IsExcluded = 0;

现在您只需更新表格一次:

UPDATE t
  SET IsExcluded = 1
  FROM dbo.Table1 AS t
  INNER JOIN dbo.ExcludedCodes AS x
  ON t.Code = x.Code;

并且您必须在两个表上都使用触发器来维护这一点。有了这个,你的查询就变成了:

SELECT @Count = COUNT(Name)
  FROM dbo.Table1 WHERE IsExcluded = 0;

编辑

至于“NOT IN 比 LEFT JOIN 慢”,这是我仅在几千行上执行的简单测试:

编辑 2

我不知道为什么这个查询不能满足你的要求,而且比你的 40K 循环效率高得多:

SELECT src.Name, COUNT(src.*)
  FROM dbo.Table1 AS src
  INNER JOIN #temptable AS t
  ON src.Name = t.Name
  WHERE src.Code NOT IN (SELECT Code FROM dbo.ExcludedCodes)
  GROUP BY src.Name;

或 LEFT JOIN 等效项:

SELECT src.Name, COUNT(src.*)
  FROM dbo.Table1 AS src
  INNER JOIN #temptable AS t
  ON src.Name = t.Name
  LEFT OUTER JOIN dbo.ExcludedCodes AS x
  ON src.Code = x.Code
  WHERE x.Code IS NULL
  GROUP BY src.Name;

我会在不到 27 分钟的时间内完成其中任何一个查询。我什至建议按顺序运行 两个 查询会比一个需要 27 分钟的查询快得多。

最后,您可以考虑使用索引视图。我不知道您的表结构以及您是否违反任何限制,但值得调查一下恕我直言。

【讨论】:

  • 感谢您的测试,我实际上在几天前想知道自己,但选择了“NOT IN”选项。我猜是不错的选择。 ^^ 至于添加 IsExcluded 位,不幸的是,我无法更改正在使用的表。我曾想过做类似的事情,但不能更改它。 >
  • #tempTable 到底是什么?我正在查看您的答案和其他答案,甚至是我自己的问题,但我似乎无法确定该表中的确切内容..
  • @BrandonStout 你说:“它在一个非常糟糕的while循环中,我让它一次循环遍历一个临时表” - 这不是一个临时表吗?它不存储您需要加入的名称吗?如果您显示您的实际代码而不仅仅是一部分,建议可能会更准确。
  • 哦!对不起,我忘了我说过。是的,我现在可以看到这种工作方式。我应该能够相当快地实现它,但是当我即将离开时,我必须在早上尝试它。明天我会在它运行后第一件事告诉你它有多大帮助。顺便说一句,感谢您的回答,即使我无法使用 IsExcluded 位,它们也提供了很多帮助。它让我对我遇到的另一个问题有了一个想法。
  • 好吧,它最终比我想添加的那一点代码更痛苦.. 哈哈,但是在我实现它并用它来删除我拥有的另一个循环之后,它是现在降到 1:33 分钟!非常感谢!
【解决方案2】:

你说这被调用了大约 40K 次。为什么?它在游标中吗?如果是这样,你真的需要一个光标。难道你不能把你想要的 @name 值放在一个临时表中并索引它然后加入它吗?

select t.name, count(t.name) 
from table t
join #name n on t.name = n.name 
where NOT EXISTS (SELECT Code FROM ExcludedCodes WHERE Code = t.code)
group by t.name

这可能会在一个查询中为您提供所有结果,并且几乎可以肯定比 40K 单独查询要快。当然如果你需要统计所有名字,那就更简单了

select t.name, count(t.name) 
    from table t
NOT EXISTS (SELECT Code FROM ExcludedCodes WHERE Code = t
group by t.name

【讨论】:

  • 不,它不在游标中..它在一个非常糟糕的while循环中我使它一次循环遍历一个临时表..我对这种方法很感兴趣,虽然我不知道了解如何让小组正常工作.. 我会研究一下这个,谢谢!
  • 虽然循环也逐行运行。
  • @BrandonStout 一个while循环是一个游标。为什么你认为它有什么不同?请阅读:sqlblog.com/blogs/aaron_bertrand/archive/2012/01/26/… ...当然有一个不需要 40,000 次迭代的基于集合的解决方案。
【解决方案3】:

NOT EXISTS 的性能通常比NOT IN 好,但您应该在您的系统上对其进行测试。

SELECT @count = COUNT(Name)
FROM Table1 t
WHERE t.Name = @name AND NOT EXISTS (SELECT 1 FROM ExcludedCodes e WHERE e.Code = t.Code)

如果不了解您的查询的更多信息,就很难提供具体的优化建议(即适合复制/粘贴的代码)。它真的需要运行 40,000 次吗?如果可行的话,听起来您的存储过程需要返工。您可以在 proc 开始时执行上述一次并将结果插入临时表中,这可以保留来自Table1 的索引,然后加入该索引而不是运行此查询。

这个特殊位甚至可能不是让您的查询运行 27 分钟的瓶颈。例如,您是在这 9000 万行上使用游标,还是在 WHERE 子句中使用标量值 UDF?

【讨论】:

  • 我会尝试这个并让你知道! (可能需要一段时间……哈哈)
  • 我在编辑时对这个问题采取了很多行动。我在 HLGEM 回答的 cmets 中看到您正在使用 while 循环。努力摆脱它,与将 NOT IN 更改为 NOT EXISTS 相比,优化该循环几乎肯定会获得更大的性能提升。
  • 不需要返回e.code 可能会稍微快一点。只需1。我会编辑;随意删除。
  • O.o 只需将其从 NOT IN 更改为 NOT EXISTS 即可切断 16 分钟.. dang.. 好的,现在尝试分组。
  • `@whytheq - 这是相当微的优化,我对它的潜在有效性持怀疑态度,但可以肯定。我刚刚粘贴了问题中的代码,并为清楚起见做了一些小改动。 @Brandon Stout - 很高兴听到。如果您还没有,我仍然建议您删除循环。
【解决方案4】:

您是否考虑过执行一次查询并将数据填充到表变量或临时表中?像

insert into #temp (name, Namecount)
values Name, Count(name)
from table1 
where name not in(select code from excludedcodes)
group by name

并且不要忘记,只要排除代码表有些静态,您就可以使用过滤索引。

【讨论】:

  • +1。我正要发布这样的东西。我认为虽然 OP 可以 尝试优化查询,但它运行 40k 次是一个更大的问题。
【解决方案5】:

开始评估执行计划。哪个是计算最重的部分? 关于两个表之间的关系,在索引列上使用 JOIN:索引将优化查询执行。

【讨论】:

    猜你喜欢
    • 2016-02-27
    • 1970-01-01
    • 2015-05-22
    • 1970-01-01
    • 1970-01-01
    • 2022-11-24
    • 1970-01-01
    • 1970-01-01
    • 2022-09-30
    相关资源
    最近更新 更多