使用“NOT IN”时子查询非常慢

Posted

技术标签:

【中文标题】使用“NOT IN”时子查询非常慢【英文标题】:Very slow subqueries when using "NOT IN" 【发布时间】:2011-08-09 16:58:58 【问题描述】:

我正在为包含在大型预先存在的 Access 数据库(压缩和修复后约 500 mb)中的数据生成报告,但我遇到了慢速子查询的问题。

数据库有一个大表,其中包含每个客户购买的记录。这是一个简单的查询,用于查找购买了蓝色小部件的客户。它在几秒钟内完成并返回大约一万条记录。

SELECT DISTINCT CustomerId 
FROM ProductSales
WHERE Product = 'BLUE' 

这是一个尝试查找购买了蓝色小部件但未购买红色小部件的客户的查询。运行大约需要一个小时。

SELECT DISTINCT CustomerId FROM ProductSales
WHERE Product = 'BLUE' 
AND CustomerId NOT IN (
    SELECT CustomerId 
    FROM ProductSales 
    WHERE Product = 'RED'
)

有没有办法重构第二个查询以使其花费几分钟而不是一个小时?

【问题讨论】:

我认为 CustomerId 字段在两个表中都有索引? 您是否尝试过 SELECT DISTINCT CustomerId FROM ProductSales WHERE Product = 'BLUE' 减去 SELECT CustomerId FROM ProductSales WHERE Product = 'RED'。我见过它确实加快了查询的情况,但是 YMMV @Marc B:这里只有一张表,但 CustomerId 已在其上编入索引。 【参考方案1】:

Access 的数据库引擎不能为Not In 使用索引,所以它肯定会很慢。使用 CustomerId 上的索引,此查询应该会快得多,因为 db 引擎可以使用该索引。

SELECT DISTINCT blue.CustomerId
FROM
    ProductSales AS blue
    LEFT JOIN
        (
            SELECT CustomerId 
            FROM ProductSales 
            WHERE Product = 'RED'
        ) AS red
    ON blue.CustomerId = red.CustomerId
WHERE
        blue.Product = 'BLUE'
    AND red.CustomerId Is Null; 

您也可以尝试Not Exists 方法,但不能保证那里的索引使用。另外,请参阅下面 David Fenton 的评论,其中更详细地讨论了性能影响。

【讨论】:

哇。从 60 分钟缩短到大约 1 秒。这比旧版本快 3600 倍。谢谢! :-) 以供将来参考,是否有一个 SQL 操作列表 Access 不会在某处使用索引? 某处可能有一个列表,但我不知道在哪里。 :-) 如果您想对此进行硬核,Google Jet ShowPlan ...它将权威地告诉您数据库引擎如何/是否在您的查询中使用索引。您可以在此处找到有关查询性能的详细讨论:msdn.microsoft.com/en-us/library/aa188211(office.10).aspx 并不是 NOT IN 和 NOT EXISTS 从不使用索引——而是你无法预测它们何时会使用以及何时不会使用。任何时候您可以将 NOT IN/EXISTS 子查询重新设计为 JOIN,您都可能会提高性能。然而,这一切都取决于结果记录集的可编辑性——在 JOIN 中使用的一些子查询会使查询不可更新。 多年来我一直在等待 Microsoft 淘汰 Access 和 JET,但他们没有,所以我必须继续挖掘像这样的旧宝石。左连接很简单,记住这是更快的方法不是。【参考方案2】:

当然,如果您没有索引,请添加索引。如果这是一个问题,可能只是有很多客户订购了红色以外的东西,但没有那么多订购蓝色;这个(未经测试的)查询试图解决这个问题。

SELECT DISTINCT CustomerId FROM ProductSales
LEFT JOIN (
  SELECT DISTINCT CustomerId cid FROM ProductSales
  LEFT JOIN (
    SELECT DISTINCT CustomerId
    FROM ProductSales
    WHERE Product = 'BLUE'
  ) foo ON CustomerId = cid
  WHERE Product = 'RED'
) bar USING (CustomerId)
WHERE cid IS NULL

【讨论】:

以上是关于使用“NOT IN”时子查询非常慢的主要内容,如果未能解决你的问题,请参考以下文章

避免重复与慢子查询

带有“not in”的 SQL 查询非常慢

MySQL:在 WHERE 子句中带有 NOT IN 的从属子查询非常慢

如何在 SQL 子查询中使用 NOT IN? [复制]

使用“NOT IN”优化 MySQL 查询

7-13IN和NOT IN 子查询