我应该如何优化这个 mysql 查询?

Posted

技术标签:

【中文标题】我应该如何优化这个 mysql 查询?【英文标题】:How should I optimize this mysql query? 【发布时间】:2011-12-14 16:46:12 【问题描述】:

我有这个查询,但它需要的时间太长,通过 NaviCat 大约需要 30 秒。可以的话怎么优化?

SELECT DISTINCT c.clientid, c.name, c.email, c.region 
FROM clients c RIGHT JOIN orders o ON c.clientid = o.clientid 
WHERE o.order_status = 'pending' 
AND c.clientid NOT IN (
    SELECT DISTINCT c.clientid 
    FROM clients c, orders o
    WHERE c.clientid = o.clientid AND o.order_status = 'paid'
    ) 
ORDER BY c.id DESC

为了更好地了解我需要什么:我有 2 个表:

clients (id, clientid, name, email, region) 
orders (id, orderid, clientid, order_amount, order_status, ….)

记录示例:

Client | Order | Status
-----------------------
C1     | O1    | (paid)
C1     | O2    | (pending)
C2     | O3    | (paid)
C3     | O4    | (pending)
C4     | O5    | (paid)
C5     | O6    | (pending)

我只需要返回C3C5

非常感谢您的回答。

【问题讨论】:

你为什么要检查两次o.order_status = ?每个订单都有一个状态吗? 您确定要参加RIGHT JOIN 吗?看来您真的想要INNER JOIN。 (实际上,您似乎真的想要一个 IN 子句,但我可以想象性能原因迫使您改用 JOIN。) @ajreal:每个订单都有一个状态,但一个客户可以有多个订单。 OP 希望找到确实有“待处理”订单并且没有任何“已付款”订单的每个客户。 如果你想优化,你还应该提供表的定义和你有什么索引。 我不明白的是为什么你有两列似乎在两个表中都有相同的目的(主键)(id 和表client 中的clientid)跨度> 【参考方案1】:

有很多方法,这里是其中的诀窍:-

SELECT c.clientid, c.name, c.email, c.region,
  SUM(IF(o.order_status = 'paid', 1, 0)) as paid
FROM clients c
INNER JOIN orders o 
ON c.clientid = o.clientid 
WHERE o.order_status IN( 'pending', 'paid')
GROUP BY c.clientid
HAVING paid = 0;

【讨论】:

哦,好主意,使用SUM,比我的更合乎逻辑:p 哇!查询耗时 5.281 秒!非常感谢!即使我添加了 ORDER BY c.id DESC 也需要 5.435 秒 没问题,但是我检查了您的其他 cmets,您只有几千条记录,也许您应该创建一个新问题来讨论您的索引策略。 具体来说,你有orders.clientid的索引吗? 5 秒太长了,除非你在手机上运行它! 您会在 50K 行中考虑吗?也许决定它在 500K 行?为自己节省一些 CPU 时间(或几天)并更早地添加索引。连接中使用的所有列都应该有一个索引。以及几乎所有用于频繁运行查询的 Where 条件。【参考方案2】:

不确定这将如何工作,但请尝试以下操作:

SELECT DISTINCT c.clientid, c.name, c.email, c.region 
FROM clients c
RIGHT JOIN orders o ON c.clientid = o.clientid AND o.order_status = 'pending'
LEFT JOIN orders o2 ON o.clientid = o2.clientid AND o.order_status = 'paid'
WHERE o2.clientid IS NULL

基本上,尝试匹配挂单和已付款订单,并仅获取失败的挂单。

在专业方面,您没有数百万个子查询。一个缺点是WHERE 剔除它们之前生成的行数可能要大得多。所以不知道是好是坏。

编辑:另外,是的,就像 cmets 中的 @ruakh 一样,我想知道为什么那里的 RIGHT JOIN... 订单可以有零客户,还是我错过了什么?

【讨论】:

我非常喜欢您的方法,但您的查询耗时 38.937 秒,而我的查询耗时 30.906 秒。最重要的是,返回的记录不同(?!)而且不应该。 1486 给你,805 给我。表 3501 中的总记录【参考方案3】:

这里有一些很棒的想法,但是在不知道数据库引擎中发生了什么的情况下尝试优化查询并不是获得最佳答案的最直接途径。有时优化只需要一个额外的索引,而不是对 SQL 的更改。

您应该做的第一件事是查看解释计划 (documentation for 5.1),然后决定是否可以更改查询或添加索引或其他内容。可能提供的答案之一是正确的,但没有执行计划,您只是在猜测。

对您的查询有一些想法。

我不明白您为什么需要 RIGHT JOIN。由于您是在客户之后,因此 INNER JOIN 应该就足够了。

任何使用 DISTINCT 或 GROUP BY 的查询都需要最终排序。如果需要排序的行数(客户 x 订单)很大,则会影响性能。如果是@ypercube 的方法可能会很好,否则@ajreal 的技巧看起来很有希望。祝你好运。

编辑:这是一个有趣的blog,关于这种类型的查询和几种方法。

【讨论】:

【参考方案4】:

这样的东西会更好:

SELECT DISTINCT c.clientid, c.name, c.email, c.region 
    FROM clients c 
INNER JOIN orders o ON c.clientid = o.clientid 
LEFT OUTER JOIN (
    SELECT cc.clientid FROM clients cc 
        INNER JOIN orders oo WHERE cc.clientid = oo.clientid AND      
        oo.order_status = 'paid'
    GROUP BY cc.clientid) cp ON cp.clientid = c.clientid
WHERE o.order_status = 'pending' 
AND cc.clientid IS NULL
ORDER BY c.id DESC

如果您的表很大,您不想在查询中使用 IN 或 OR,它们将不允许 mysql 使用索引,另外,在您的子查询中您没有使用内连接,这是错误的。

【讨论】:

【参考方案5】:

使用EXISTS

SELECT c.clientid, c.name, c.email, c.region 
FROM clients c 
WHERE EXISTS
      ( SELECT *
        FROM orders o 
        WHERE o.clientid = c.clientid 
          AND o.order_status = 'pending'
      ) 
  AND NOT EXISTS
      ( SELECT *
        FROM orders o 
        WHERE o.clientid = c.clientid 
          AND o.order_status = 'paid'
      ) 
ORDER BY c.id DESC

使用JOIN

SELECT c.clientid, c.name, c.email, c.region 
FROM clients c 
  JOIN orders o
    ON  o.clientid = c.clientid 
    AND o.order_status = 'pending'
  LEFT JOIN orders o2
    ON  o2.clientid = c.clientid 
    AND o2.order_status = 'paid'
WHERE o2.clientid IS NULL
GROUP BY c.clientid
ORDER BY c.id DESC

我不明白的是为什么您在两个表中都有两列似乎用于相同的目的(主键)(表 client 中的idclientid 和表 order 中的相同) .

【讨论】:

第一个查询并不是那么高效。 WHERE 子句中的子查询必须针对从主查询返回的每一行运行。如果您有成千上万的客户,那将会受到伤害。第二个更好。 @FranciscoSoto:这是一种预感还是您已经对此进行了测试? @FranciscoSoto:请参阅 Quassnoi 的答案以了解绕过此问题的方法以及为什么它可能会更好(或更糟)的解释:***.com/questions/1766702/… Exists 耗时 20.062 秒,连接耗时 13.844 秒,如果我查看 38 秒的查询就足够了,谢谢您的帮助 @FranciscoSoto 连接必须对基表中的每一行进行查找。 Oracle 和 DB2 等数据库(不了解 MySQL)有时可以将 EXISTS 优化为连接(甚至进行短路评估)。最重要的是,您必须知道数据库在做什么。

以上是关于我应该如何优化这个 mysql 查询?的主要内容,如果未能解决你的问题,请参考以下文章

mysql性能优化-慢查询分析,优化索引最佳实践

我如何在mysql中优化这个查询?

我应该如何优化这个 SQL 查询?

如何优化 MySQL 数据库/查询

如何优化这个 MySql 查询 - 连接 3 个表?

如何使用索引优化我的 MySQL 查询