起因
有个1700w的表需要初始化 , 然后我们需要分批取id范围是[1 , 1000) , [1000 , 2000)的值
问题
很简单的sql
update test set a.value=1 where id in ( 1 , 2 , 7 , 9.... 1000);
update test set a.value=1 where id in ( 1001 , 1002 , 1005 , ... 2000);
这里的id大概有100个左右 ,id是单调递增,基本连续
测试环境很正常,非常快 , 通过这个sql , 我们可以一秒update 1w以上的行
但是生产环境这个update特别特别慢,update 1000 行 大概需要 50s
以上
排查
- 定位
经过很多尝试,
定位到是update这个sql特别慢,而且是但是测试环境非常快,生产环境非常慢
尝试explain
explain
update test as a set a.value=1 where id in ( 1 , 2 , 7 , 9....);
生产环境下是这样:
Using where; Using temporary
但是测试环境是:
Using where
开始搜索,找到了类似的原因:
https://bugs.mysql.com/bug.php?id=80424
对比了一下版本:
生产环境:5.7.9-log
测试环境:5.7.22-log
确定binlog的记录形式:
SELECT @@binlog_row_image
结果是
FULL
这个bug被5.7.15以上修复,所以测试环境没有问题,生产环境有问题
解决
因为生产版本的mysql几乎没有升级的可能,这个批量的刷数据如果10条/s估计要刷一个星期,所以我们尝试了很多写法避免这个优化,最后使用了这个写法避免 生产版本的mysql的bug
不使用in 而是使用join 防止这个优化器的bug
DESC
UPDATE `test` a JOIN (
SELECT id FROM test t WHERE `id` IN (516,517,518,519,520,521,522,523,524,525,526,527,528,529,530,531,533,532)
) t ON a.id = t.id
SET a.isvisible = -1;
优化后不用temp了
"id" "select_type" "table" "partitions" "type" "possible_keys" "key" "key_len" "ref" "rows" "filtered" "Extra"
"1" "UPDATE" "a" \N "range" "PRIMARY" "PRIMARY" "4" \N "104" "100.00" "Using where"
"1" "SIMPLE" "b" \N "eq_ref" "PRIMARY" "PRIMARY" "4" "a.id" "1" "100.00" "Using index"
事后扒代码
通过https://bugs.mysql.com/bug.php?id=80424 提供的patch大概定位到原因
为什么会使用temp表?
第六个参数是判断是否需要使用temp的 ,也就是 !using_filesort && (used_key_is_modified || order)
Modification_plan plan(thd, MT_UPDATE, &qep_tab,
used_index, limit,
(!using_filesort && (used_key_is_modified || order)),
using_filesort, used_key_is_modified, rows);
查看Modification_plan
这个类的定义:
Modification_plan(THD *thd_arg,
enum_mod_type mt, QEP_TAB *qep_tab,
uint key_arg, ha_rows limit_arg, bool need_tmp_table_arg,
bool need_sort_arg, bool used_key_is_modified_arg,
ha_rows rows);
在这个问题中是 used_key_is_modified = true
, 所以会产生temp表
相关阅读: