mysql修改字符集utf8mb4引发的惨案

mysql修改字符集utf8mb4引发的惨案

  • 环境配置: Linux CentOS 7 mysql5.7字符编码是utf8;
  • 惨案原因:数据库表需要支持表情很符号,表情符号一般是4个字符,utf8最多支持3个字符,如果插入表情符号4个字符的字段就会报错,因此我们修改了此表的字符集为utf8mb4,在这说明一下utf8mb4是utf8的超集。
  • 问题来了:MySQL环境上有两张表做left join时使用的字段都索引,但是执行计划里面显示有一张表使用了全表扫描,扫描全表近百万行记录,导致sql执行很慢。
  • 诊断结果:是mysql表修改字符集utf8mb4引发的惨案;
  • 问题诊断重现:首先,表结构和表记录如下:
CREATE TABLE `t1` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(20) DEFAULT NULL,
`code` varchar(50) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_code` (`code`),
KEY `idx_name` (`name`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8

t1 插入一些数据

CREATE TABLE `t2` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(20) DEFAULT NULL,
`code` varchar(50) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_code` (`code`),
KEY `idx_name` (`name`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4

t2插入一些数据

2张表left join的执行计划如下:

desc select * from t2 join t1 on t1.code = t2.code where t2.name = 'dddd'\G;

  • 可以明显地看到,t2.name ='dddd’使用了索引,而t1.code = t2.code这个关联条件没有使用到t1.code上面的索引,一开 始也百思不得其解,但是机器不会骗人。显示警告查看
    查询执行计划的告警信息:
    show warnings;

  • 发现问题:在发现了转换(使用utf8mb4的testdb.t1.code)之后,Scott发现2个表的字符集不一样。t1为utf8,t2为utf8mb4。但是为什么表表字符集不一样(实际是我修改了字符集 字符串字符集不一样了)就会导致T1全表扫描呢?下面来做分析。
    (1)首先t2左联接t1决定了t2是驱动表,此步相当于执行了选择* from t2 where t2.name =‘dddd’,取码范围的值,这里为’8a77a32a7e0825f7c8634226105c42e5’;
    (2)然后拿起t2查到的代码的值根据联接条件去t1里面查找,这一步就相当于执行了select * from t1 where t1.code =‘8a77a32a7e0825f7c8634226105c42e5’;
    (3)但是由于第(1)步里面T2表取出的代码字段是utf8mb4字符集,而T1表里面的代码是UTF8字符集,这里需要做字符集转换,字符集转换遵循由小到大的原则,因为utf8mb4是UTF8的超集,所以这里把UTF8转换成utf8mb4,即把t1.code转换成utf8mb4字符集,转换了之后,由于t1.code上面的索引仍然是UTF8字符集,所以这个索引就被执行计划忽略了,然后T1表只能选择全表扫描。更糟糕的是,如果T2筛选出来的记录不止1条,那么T1就会被全表扫描多次,性能之差可想而知。

  • 解决问题:
    既然原因已经清楚了,如何解决呢?当然是改字符集了,把T1改成和T2一样或者把T2改成T1都可以,这里选择把T1转成utf8mb4。那怎么转字符集呢?
    有一个同学会说用alter table t1 charset utf8mb4;而是错的,这只是改了表的替代字符集,即新的数据才会使用utf8mb4,已经存在的一部分仍然是utf8。
    用只有alter table t1 convert to charset utf8mb4;才是正确的。
    但是还要注意一点,更改表改字符集的操作是双重写的(用lock = node会报错)所以业务高峰时请不要操作,即使在业务低峰时期,大表的操作仍建议使用pt-online -schema改变在线修改字符集。
    测试环境:使用 alter table t1 convert to charset utf8mb4, lock=shared;

现在再来查看执行计划,可以看到已经没问题了。

  • 复盘总结:
    1、 表字符集不同时,可能导致加入的SQL使用不到索引,引起严重的性能问题;
    2、 改字符集的alter table操作会多个写,业务mysql建议使用pt-online-schema-change;
    3、 如果要大批量修改表的字符集,同样做好SQL的审议工作,关联的表的字符集一起做修改。
    4、 效仿一下使用show warnings【千万不能忘】

猜你喜欢

转载自blog.csdn.net/qq_31555951/article/details/106615110
今日推荐