【问题标题】:MySQL: long running LEFT JOIN query performanceMySQL:长时间运行的 LEFT JOIN 查询性能
【发布时间】:2019-07-09 21:38:42
【问题描述】:

一个 MySQL 数据库包含两个表:customer 和 custmomer_orders

customer 表包含 8000 万个条目和 80 个字段。其中一些我感兴趣:

  1. Id (PK, int(10))
  2. 位置(varchar 255,可为空)。
  3. Registration_Date(日期时间,可为空)。已编入索引。

customer_orders 表包含 4000 万个条目,并且仅包含 3 个字段:

  1. Id (PK, int(10))
  2. Customer_Id(int(10),FK 到客户表)
  3. Order_Date(日期时间,可为空)

当我运行这样的查询时,它需要 ~800 秒来执行并返回 4000 万个条目:

SELECT o.* 
FROM customer_orders o
LEFT JOIN customer c ON (c.Id = o.Customer_Id) 
WHERE NOT (ISNULL(c.Location)) AND c.Registration_Date < '2018-01-01 00:00:00';

装有 MySQL 服务器的机器有 32GB 的 RAM,28GB 分配给 MySQL。 MySQL 版本:5.6.39。

MySQL 在有这么多记录的表上执行这么长时间的查询是否正常? 如何提高性能?

更新:

customer_orders 表不包含我们想要存储的任何重要数据。这是最近 10 天内下订单的某种复制表。 我们每天都会运行一个存储过程,该过程会删除事务范围内超过 10 天的订单。

在某个时刻,这个存储过程由于没有优化查询而超时,并且订单数量每天都在增长。 以前的查询还包含 COUNT 方法,我想它超过了超时。

然而,令我惊讶的是,MySQL 最多需要 15 分钟才能在附加条件下获取 40m 条记录。

【问题讨论】:

  • (1) 只选择你真正需要的字段; (2) 该查询似乎没有理由使用 LEFT JOIN 而不是 INNER JOIN,您的 WHERE 条件有效地使其成为 INNER JOIN; (3) 连接中使用的字段以及条件应该被索引的地方。
  • 订单比客户少,令人印象深刻
  • 没有必要再加入一个您不从中选择列的表。但是,您的 WHERE 子句将您的 LEFT JOIN 呈现为 INNER JOIN,因此您不妨一开始就这样写。

标签: mysql sql join query-optimization query-performance


【解决方案1】:

我想发表评论,但我改变主意要回答。

因为主要问题是您的问题本身。

我不知道你的customer_orders 有多少列,但如果你得到了

4000 万个条目

返回。我会说你做错了什么。 这可能不是查询本身很慢,而是数据获取。

为了证明尝试对您的查询执行EXPLAIN:

EXPLAIN SELECT ...your query here... ;

然后执行

EXPLAIN SELECT ...your query here... LIMIT 1;

例如尝试LIMIT你的结果为1000:

SELECT ...your query here... LIMIT 1000;

当您有这些查询的答案、输出和统计信息时,我们可以讨论您的以下步骤。

【讨论】:

  • EXPLAIN 通常会忽略LIMIT。所以我不会在第二个EXPLAIN 中预测任何有用的信息。
【解决方案2】:

我认为这很正常。如果您分享 explain 针对该查询返回的内容,将会很有帮助。

为了优化查询,从 customer_orders 开始可能不是一个好主意,因为无论如何您都没有过滤它(因此它对 40M 记录执行全表扫描)。此外,正如 cmets 中所指出的,这里不需要 LEFT JOIN。 我会这样写你的查询:

SELECT o.*
FROM customers c, customer_orders o
WHERE c.id = o.Customer_Id
AND   c.Location IS NOT NULL
AND   c.Registration_Date < '2018-01-01'

这将(取决于有多少记录满足Registration_Date &lt; '2018-01-01' 子句)首先过滤customers 表,然后加入具有customer_id 索引的customer_orders 表

另外,可能不相关,但是查询返回 40M 记录对您来说正常吗?我的意思是,它就像整个customer_orders 表。如果我是对的,这意味着所有订单都来自客户在 '2018-01-01'

之前注册

【讨论】:

    【解决方案3】:

    如果我的评论和 GMB 的回答最终对性能没有太大帮助;您可以随时尝试使用不同的方法编写查询。我通常更喜欢连接而不是子查询,但有时它们会成为处理数据的最佳选择。

    由于您说customers 表与orders 表相比相对较大,这可能是其中一种情况。

    SELECT o.* 
    FROM customer_orders AS o
    WHERE o.Customer_Id IN (
         SELECT Id 
         FROM customer 
         WHERE Location IS NOT NULL 
            AND Registration_Date < '2018-01-01 00:00:00'
    );
    

    【讨论】:

    • 好点,谢谢。但不幸的是,这也花费了大约 550 秒。
    • IN ( SELECT ... ) 效率非常低。
    • @RickJames 是的,这很少(如果有的话)是我的第一选择;但在某些情况下,它可能是最好的。
    【解决方案4】:

    太期待评论了……

    关于您的查询首先要注意的是,它实际上并未执行LEFT JOIN,因为它在WHERE 子句中具有引用LEFT JOINed 表的条件。

    可以改写为:

    SELECT o.* 
    FROM customer_orders o
    INNER JOIN customer c 
        ON c.Id = o.Customer_Id
        AND c.Location is NOT NULL
        AND c.Registration_Date < '2018-01-01 00:00:00';
    

    明确连接类型有助于提高可读性,并有助于 MySQL 为查询找到更好的执行路径。

    在性能方面,基本建议是,对于此查询,您需要在所有被搜索的三列上建立一个复合索引,其顺序与查询中使用的顺序相同(通常,您希望将更严格的条件放在开头,因此您可能需要调整它):

    ALTER TABLE mytable ADD INDEX (Id, Location, Registration_Date );
    

    有关性能方面的更多建议,您可能希望使用表的 CREATE TABLE 语句和查询的执行计划来更新您的问题。

    【讨论】:

    • WHERE 中的子句顺序 不 重要; INDEX 中的顺序执行。一旦你点击了“范围”子句,INDEX 的其余部分就没有用了。 Location IS NOT NULL 和 Date &lt; ... 都是范围。例外情况是INDEX 是“覆盖”,在这种情况下就是这样。在这种情况下,我会将Location 或Registration_Date 放在首位,因为它位于WHERE 中并与常量进行比较。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-03-31
    • 2018-10-06
    • 1970-01-01
    • 2021-10-16
    • 1970-01-01
    • 2019-08-25
    • 2015-02-22
    相关资源
    最近更新 更多