MySQL update语句带子查询的优化探寻

MySQL update带IN子查询慢查询优化

0 背景

某个需求下有两张表(已简化):


sql

代码解读

复制代码

CREATE TABLE `record` ( `id` int(11) NOT NULL AUTO_INCREMENT, `uuid` varchar(36) NOT NULL, `status` tinyint(3) unsigned NOT NULL DEFAULT '0', `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, `updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `record_uuid_uindex` (`uuid`) ); CREATE TABLE `bill` ( `id` int(11) NOT NULL AUTO_INCREMENT, `apply_id` varchar(36) NOT NULL, `record_uuid` varchar(36) NOT NULL, `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, `updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `bill_record_uuid_index` (`record_uuid`), KEY `bill_apply_id_index` (`apply_id`)

假设需求要将某个 apply_id 关联的 record 记录 status 值设为2,update语句如下:


sql

代码解读

复制代码

UPDATE record SET status=2 WHERE uuid in ( SELECT record_uuid FROM bill WHERE apply_id='xxxxxx' );

这样的SQL咋一看,record.uuid、bill.apply_id 均有索引,似乎并没有什么问题,但实际上线后(record表千万数量级)却会执行超时(5秒)。

1 探寻慢查询原因

测试环境 MySQL 版本 5.7.21,构造假数据量如下:

其中 apply_id='b1e83226-d827-4132-a4ef-e8d0e929179b' 关联的record数据仅3行

SELECT * FROM record WHERE uuid IN (SELECT record_uuid FROM bill WHERE apply_id='b1e83226-d827-4132-a4ef-e8d0e929179b');

查看该 SELECT 语句执行计划,可以看到索引使用和扫描行数是正常的:

MySQL优化器将该语句优化成了联表查询:

对应的update语句耗时和执行计划:

执行计划咋一看有走索引,实际上从key和扫描行数rows可看出走了全表扫描,导致执行时长需0.12秒。

为何会走全表扫描?

既然key用了主键,那么用主键做关联会有改善吗?建表 bill_id,其中 record_id 关联 record 表主键


sql

代码解读

复制代码

CREATE TABLE `bill_id` ( `id` int(11) NOT NULL AUTO_INCREMENT, `apply_id` varchar(36) NOT NULL, `record_id` int(11) NOT NULL, `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, `updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `bill_id_apply_id_index` (`apply_id`), KEY `bill_id_record_id_index` (`record_id`)

结果如下,表现一致,仍就走了全表扫描。

由于 show warnings; 只对 SELECT 语句能看到优化器结果,借助第三方软件 

可见优化器将 IN 子查询优化成了 EXISTS,用对应 SELECT 语句验证执行计划:

确实是走了全表扫描。

2 优化方向

先select后update

最直观的方式自然是将子查询拆分出来,先select再update:

更新行数较多时,终究会多出一条 SELECT 语句和 IN 内容过长的损耗。

联表

使用 join 联表后更新:

索引使用和执行时长都符合预期。

3 小结

可见 MySQL 5.7 对 update 语句带 IN 子查询的优化是比较差劲的,即使条件涉及的字段均有索引,优化器也会将语句优化成 EXISTS 条件,导致外表全表扫描;可改成 join 联表语句进行更新。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值