【问题标题】:Understanding EXPLAIN SELECT to optimize MySQL query了解 EXPLAIN SELECT 以优化 MySQL 查询
【发布时间】:2021-11-16 08:08:24
【问题描述】:

我有这个表架构:

CREATE TABLE `exchange` (
  `id` bigint unsigned NOT NULL AUTO_INCREMENT,
  `exchange` double unsigned NOT NULL,
  `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `platform_id` bigint unsigned NOT NULL,
  `product_id` bigint unsigned NOT NULL,
  PRIMARY KEY (`id`),
  KEY `exchange_created_at_index` (`created_at`),
  KEY `exchange_product_id_created_at_id_index` (`product_id`,`created_at`,`id`),
  KEY `exchange_product_id_created_at_platform_id_id_index` (`product_id`,`created_at`,`platform_id`,`id`),
  KEY `exchange_platform_fk` (`platform_id`),
  KEY `exchange_platform_id_created_at_id_index` (`platform_id`,`created_at`,`id`),
  CONSTRAINT `exchange_platform_fk` FOREIGN KEY (`platform_id`) REFERENCES `platform` (`id`) ON DELETE CASCADE,
  CONSTRAINT `exchange_product_fk` FOREIGN KEY (`product_id`) REFERENCES `product` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

这个表大约有 14.761.479 行

我正在尝试优化这个查询:

SELECT *
FROM `exchange`
WHERE (
    `created_at` >= '2021-09-17 22:36:11'
    AND `platform_id` = 1
    AND `id` IN (
        SELECT MIN(`id`)
        FROM `exchange`
        WHERE `created_at` >= '2021-09-17 22:36:11'
        GROUP BY `product_id`
    )
);

425 rows in set (14,69 sec)

仅子查询大约需要 2 秒:

SELECT MIN(`id`)
FROM `exchange`
WHERE `created_at` >= '2021-09-17 22:36:11'
GROUP BY `product_id`;

729 rows in set (2,11 sec)

解释是:

*************************** 1. row ***************************
           id: 1
  select_type: PRIMARY
        table: exchange
   partitions: NULL
         type: ref
possible_keys: exchange_created_at_index,exchange_platform_fk,exchange_platform_id_created_at_id_index
          key: exchange_platform_fk
      key_len: 8
          ref: const
         rows: 6955794
     filtered: 50.00
        Extra: Using where
*************************** 2. row ***************************
           id: 2
  select_type: SUBQUERY
        table: exchange
   partitions: NULL
         type: index
possible_keys: exchange_created_at_index,exchange_product_id_created_at_id_index,exchange_product_id_created_at_platform_id_id_index
          key: exchange_product_id_created_at_id_index
      key_len: 21
          ref: NULL
         rows: 13911589
     filtered: 50.00
        Extra: Using where; Using index
2 rows in set, 1 warning (0,00 sec)

和警告:

  Level: Note
   Code: 1003
Message: /* select#1 */ select `crypto`.`exchange`.`id` AS `id`,`crypto`.`exchange`.`exchange` AS `exchange`,`crypto`.`exchange`.`created_at` AS `created_at`,`crypto`.`exchange`.`platform_id` AS `platform_id`,`crypto`.`exchange`.`product_id` AS `product_id` from `crypto`.`exchange` where ((`crypto`.`exchange`.`platform_id` = 1) and (`crypto`.`exchange`.`created_at` >= TIMESTAMP'2021-09-17 22:36:11') and <in_optimizer>(`crypto`.`exchange`.`id`,`crypto`.`exchange`.`id` in ( <materialize> (/* select#2 */ select min(`crypto`.`exchange`.`id`) from `crypto`.`exchange` where (`crypto`.`exchange`.`created_at` >= TIMESTAMP'2021-09-17 22:36:11') group by `crypto`.`exchange`.`product_id` having true ), <primary_index_lookup>(`crypto`.`exchange`.`id` in <temporary table> on <auto_distinct_key> where ((`crypto`.`exchange`.`id` = `<materialized_subquery>`.`MIN(``id``)`))))))
1 row in set (0,00 sec)

为什么在 PRIMARY 查询中 EXPLAIN key 仅是 exchange_platform_fk 而不是 exchange_platform_id_created_at_id_index

我需要添加哪些索引来优化这个查询?

将 WHERE 条件移至子查询最差:

SELECT *
FROM `exchange`
WHERE (
    `id` IN (
        SELECT MIN(`id`)
        FROM `exchange`
        WHERE (
            `created_at` >= '2021-09-17 22:36:11'
            AND `platform_id` = 1
        )
        GROUP BY `product_id`
    )
);

425 rows in set (19,86 sec)
*************************** 1. row ***************************
           id: 1
  select_type: PRIMARY
        table: exchange
   partitions: NULL
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 13911589
     filtered: 100.00
        Extra: Using where
*************************** 2. row ***************************
           id: 2
  select_type: SUBQUERY
        table: exchange
   partitions: NULL
         type: ref
possible_keys: exchange_created_at_index,exchange_product_id_created_at_id_index,exchange_product_id_created_at_platform_id_id_index,exchange_platform_fk,exchange_platform_id_created_at_id_index,exchange_platform_id_created_at_index
          key: exchange_platform_fk
      key_len: 8
          ref: const
         rows: 6955794
     filtered: 50.00
        Extra: Using where; Using temporary
2 rows in set, 1 warning (0,00 sec)

谢谢!

【问题讨论】:

  • 很难找到提供正确信息的查询优化问题。你的可以。谢谢。

标签: mysql query-optimization explain


【解决方案1】:

您的子查询完成了繁重的工作。这是

        SELECT MIN(id)
        FROM exchange
        WHERE created_at >= '2021-09-17 22:36:11'
        GROUP BY product_id

您需要覆盖此子查询的索引位于(created_at, product_id, id ASC)。为什么?

  1. 您希望仅从索引中获取整个结果,而无需在表中查找数据。
  2. 您可以将这些索引(使用 B-Tree 布局)视为按顺序排序。
  3. 此子查询根据您的created_at WHERE 条件随机访问第一个符合条件的行的索引。
  4. 然后它按顺序读取索引。按product_id 分组的顺序很好。
  5. 因为你在找MIN(id),所以它可以很快找到loose index scan

当您构造索引以匹配查询时,首先放置相等匹配的列,然后放置范围映射的列。所以,对于

SELECT a,b,c, FROM tbl WHERE a=1 AND b=2 AND c> 10

您想要在(a,b,c) 上建立索引。

顺便说一句,如果您在(a,b,c) 上有一个索引,那么就不需要在(a)(a,b) 上创建一个索引。除了减慢插入和更新速度之外,它没有任何作用。

注意:在 InnoDB 中,主键 id 已经是索引的一部分。因此,您可以将其作为我建议的索引中的最后一列省略。试试看吧。

阅读此内容:https://use-the-index-luke.com/ 欢迎来到索引争论的神秘世界。

【讨论】:

  • 我已将子查询执行详细信息添加为 PRIMARY。 14 秒只有 2 秒。谢谢!
【解决方案2】:

试试这个重新表述:

SELECT  *
    FROM  
    (
        SELECT  e1.product_id, MIN(e1.id) AS min_id
            FROM  `exchange` AS e1
            WHERE  e1.`platform_id` = 1
              AND  e1.`created_at` >= '2021-09-17 22:36:11'
            GROUP BY  e1.product_id 
    ) AS x
    JOIN  `exchange` AS e  ON e.id = x.min_id ;

exchange 的索引一起:

INDEX(platform_id, created_at, product_id, id)

它是针对子查询的。

  • platform_id 是用= 测试的,所以需要先来。
  • created_at 负责 WHERE 的其余部分。
  • 其他两列在最后;他们使索引“覆盖”查询。

注意我使用的哲学:

  1. 找到所需的 ID。这个列表可能会小于exchange 的所有行。
  2. 通过有效地使用PRIMARY KEY(id) 查找*

IN (SELECT...) 有时优化得很差。)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-08-09
    • 2016-06-11
    • 1970-01-01
    • 1970-01-01
    • 2015-09-14
    • 2012-08-06
    • 2012-04-26
    • 2011-12-06
    相关资源
    最近更新 更多