问题描述
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分钟-肯定不能接受:)也许我可以以某种方式加快它的速度?
解决方法
这将花费很长时间。这是正在发生的事情。它似乎是最佳的:
- 通过BTree获得唯一键
order_id
(order_id
,bulletin_id
)。由于order_id=2
,这本质上是线性扫描。 - 这提供了约3M个
bulletin_id
值。 - 对于每一个,钻入
bulletins
的BTree中以查找行。 - 选中
done=0 and error=0
。 - 如果检查通过,则检索
category_id
(需要的4列中的最后一列)
步骤3可能是最慢的部分。它可能涉及多达2M的磁盘读取,具体取决于值的随机分布情况。最坏的情况是,大约有32GB被读取(和/或重新读取)到内存中。 (充其量而言,所有块都已被缓存,并且没有I / O。)
听起来您有旋转磁盘(HDD),而不是SSD?
您有多少RAM?更多的RAM可能会有所帮助。 innodb_buffer_pool_size
的设置是什么?如果它没有应有的大小,则将其增加到大约70%的RAM。
由于您拥有NVMe SSD,所以我不能将缓慢的原因归咎于I / O。所以...我不知道在步骤4中从json字符串中提取done
和error
会花费多少时间。
如果查询是CPU绑定而不是I / O绑定,则提取是问题。将这两列从VIRTUAL
更改为STORED
,并将数据类型更改为最小的数据类型,也许是TINYINT
。注意:ALTER
可能要花费几个小时。
(注意:查询仅使用1个CPU内核。)
由于当前未使用该索引,所以让我们尝试使用“覆盖”索引来欺骗它:INDEX(done,error,category_id,id)
。如果未自动使用该索引,请添加索引“提示”。