如何使用join优化mysql查询

问题描述

SELECT
    `bulletins`.`id`,`category_id`,`orders_bulletins`.`order_id` AS `pivot_order_id`,`orders_bulletins`.`bulletin_id` AS `pivot_bulletin_id`
FROM
    `bulletins`
INNER JOIN `orders_bulletins` ON `bulletins`.`id` = `orders_bulletins`.`bulletin_id`
WHERE
    `orders_bulletins`.`order_id` = 2 AND(`done` = 0 AND `error` = 0)

Bulletins表具有约35毫米的行

索引:

公告:

CREATE TABLE `bulletins` (
 `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,`data` longtext CHaraCTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL CHECK (json_valid(`data`)),`item_data` longtext CHaraCTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT '0',`phone` bigint(20) unsigned DEFAULT NULL,`done` bigint(20) GENERATED ALWAYS AS (coalesce(json_value(`item_data`,'$.id'),0)) VIRTUAL,`error` int(10) GENERATED ALWAYS AS (coalesce(json_value(`item_data`,'$.error.code'),`time` bigint(20) unsigned GENERATED ALWAYS AS (json_value(`data`,'$.time')) VIRTUAL,PRIMARY KEY (`id`),KEY `phone` (`phone`),KEY `id` (`phone`,`id`) USING BTREE,KEY `done` (`done`,`error`,`time`),) ENGINE=InnoDB AUTO_INCREMENT=2047904069 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=COMpressed

orders_bulletins:

CREATE TABLE `orders_bulletins` (
 `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,`order_id` bigint(20) unsigned NOT NULL,`bulletin_id` bigint(20) unsigned NOT NULL,UNIQUE KEY `order_id` (`order_id`,`bulletin_id`),KEY `bulletin_id` (`bulletin_id`)
) ENGINE=InnoDB AUTO_INCREMENT=82218220 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci

上面的查询大约需要90分钟-肯定不能接受:)也许我可以以某种方式加快它的速度?

说明:

enter image description here

解决方法

这将花费很长时间。这是正在发生的事情。它似乎是最佳的:

  1. 通过BTree获得唯一键order_idorder_idbulletin_id)。由于order_id=2,这本质上是线性扫描。
  2. 这提供了约3M个bulletin_id值。
  3. 对于每一个,钻入bulletins的BTree中以查找行。
  4. 选中done=0 and error=0
  5. 如果检查通过,则检索category_id(需要的4列中的最后一列)

步骤3可能是最慢的部分。它可能涉及多达2M的磁盘读取,具体取决于值的随机分布情况。最坏的情况是,大约有32GB被读取(和/或重新读取)到内存中。 (充其量而言,所有块都已被缓存,并且没有I / O。)

听起来您有旋转磁盘(HDD),而不是SSD?

您有多少RAM?更多的RAM可能会有所帮助。 innodb_buffer_pool_size的设置是什么?如果它没有应有的大小,则将其增加到大约70%的RAM。

由于您拥有NVMe SSD,所以我不能将缓慢的原因归咎于I / O。所以...我不知道在步骤4中从json字符串中提取doneerror会花费多少时间。

如果查询是CPU绑定而不是I / O绑定,则提取是问题。将这两列从VIRTUAL更改为STORED,并将数据类型更改为最小的数据类型,也许是TINYINT。注意:ALTER可能要花费几个小时。

(注意:查询仅使用1个CPU内核。)

由于当前未使用该索引,所以让我们尝试使用“覆盖”索引来欺骗它:INDEX(done,error,category_id,id)。如果未自动使用该索引,请添加索引“提示”。