MySQL 不相关子查询索引失效问题分析 - 字段编码方式不同导致

一、问题描述

涉及的两张表表结构如下:

CREATE TABLE `ep_ding_talk_class_relation` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id',
  `ding_talk_class_id` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '',
  `care_class_id` char(32) NOT NULL DEFAULT '' COMMENT '',
  `corp_id` varchar(128) NOT NULL DEFAULT '' COMMENT '',
  PRIMARY KEY (`id`),
  UNIQUE KEY `udx_care_class_id` (`care_class_id`),
  UNIQUE KEY `udx_ding_talk_class_id_corp_id` (`ding_talk_class_id`,`corp_id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=817 DEFAULT CHARSET=utf8mb4 COMMENT=''
CREATE TABLE `ep_im_group_classroom` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `resourceid` varchar(45) CHARACTER SET utf8 NOT NULL,
  `classroom_id` varchar(45) CHARACTER SET utf8 DEFAULT NULL,
  `class_nick_name` varchar(45) NOT NULL DEFAULT '',
  `class_master_id` varchar(45) CHARACTER SET utf8 NOT NULL DEFAULT '',
  PRIMARY KEY (`id`),
  UNIQUE KEY `res` (`resourceid`) USING BTREE,
  UNIQUE KEY `uk_classroom_id` (`classroom_id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1046177 DEFAULT CHARSET=utf8mb4

SQL语句以及explain后的结果如下:


MySQL 不相关子查询索引失效问题分析 - 字段编码方式不同导致_第1张图片
image.png

查询优化器没有像预想的那样走uk_classroom_id索引。

二、问题分析及解决

之前听说过,MySQL在执行子查询时,可能会优化SQL语句,抱着试一试的心态查看了优化后的SQL语句,一下就找到了问题。
explain后执行show warnings; 得到MySQL优化后的SQL:


MySQL 不相关子查询索引失效问题分析 - 字段编码方式不同导致_第2张图片
image.png

如上图所示,SQL语句被优化成convert(classroom_id using utf8mb4) 后再与care_class_id字段比较,是需要将ep_im_group_classroom表的classroom_id全部convert一次的,自然就全表扫描了(走索引也是把索引上的classroom_id全都convert一遍的)。

解决方式

方式一:

将ep_im_group_classroom表classroom_id字段的编码方式改为utf8mb4,这涉及到改表的结构,也需要评估其他风险,比如这张表是不是还和别的表有连接查询、子查询操作?改编码方式会不会导致其他查询语句索引失效?

方式二:

将子查询结果的care_class_id字段convert成utf8。如下图:


MySQL 不相关子查询索引失效问题分析 - 字段编码方式不同导致_第3张图片
image.png

查询ep_im_group_classroom表时是走了索引的。(ep_ding_talk_class_relation表本来就没有高效的索引可走的,corp_id字段没索引)
这种解决方式也是有风险的,care_class_id字段原本的编码方式是utf8mb4,与utf8相比,utf8mb4可以存的字符种类更多,比如utf8mb4可以存emoji表情,utf8就不行,如果care_class_id字段存了emoji字符时,将其convert成utf8就会出错。这里可以这么做,是因为care_class_id字段必定为字母、数字组成的uuid。

三、一些思考

1.关于不同编码方式字符串的比较规则

说起来有些惭愧,一直以来都没了解过不同编码方式的字符串是怎么比较的,以为就是单纯的二进制比较。所以我一开始的疑问是:为什么需要转换字符集再比较?直接无视编码方式用二进制比较不就行了?
实际上,gbk编码方式的“我” 和 utf8mb4编码方式的“我” ,两者二进制不同,但是比较的结果是要相等,所以需要做一次转换再比较。

Unicode : 是一个字符集,规定了字符的二进制码,没规定这个二进制代码要怎么存。
比如汉字“严”的Unicode二进制是100111000100101,
GBK编码方式使用两个字节保存这个二进制码,结果的十六进制为D1CF ,
UTF8编码方式使用三个字节保存这个二进制码,结果的十六进制为E4B8A5。
编码方式的转换其实就是:
(1)用当前编码方式,解析出字符的Unicode码;
(2)用目标编码方式保存该字符的Unicode码。

关于编码方式转换,如果不清楚,可以学习一下这篇:
http://www.ruanyifeng.com/blog/2007/10/ascii_unicode_and_utf-8.html

2.只要对字符集不同的两个字段做连接查询,索引就一定会失效吗?

我把classroom_id和care_class_id两个字段的字符集互换,再看看explain的执行情况。


MySQL 不相关子查询索引失效问题分析 - 字段编码方式不同导致_第4张图片
image.png

MySQL 不相关子查询索引失效问题分析 - 字段编码方式不同导致_第5张图片
image.png
explain的结果:
MySQL 不相关子查询索引失效问题分析 - 字段编码方式不同导致_第6张图片
image.png
优化器优化后的SQL:
MySQL 不相关子查询索引失效问题分析 - 字段编码方式不同导致_第7张图片
image.png

可以看出,classroom_id使用utf8mb4字符集,care_class_id使用utf8字符集时,查询语句被优化成
convert(care_class_id using utf8mb4),查询ep_im_group_classroom表时走了uk_classroom_id索引。

convert()哪一个字段,取决于它们的字符集哪个是子集,哪个是超集。
utf8是utf8mb4的子集,所以是convert()字符集为utf8的字段。

3.一定要是连接查询,索引才会因为字符集不同失效吗?

MySQL 不相关子查询索引失效问题分析 - 字段编码方式不同导致_第8张图片
image.png

MySQL 不相关子查询索引失效问题分析 - 字段编码方式不同导致_第9张图片
image.png

可见,该子查询未被优化成连接查询,也因为字符集的原因没走uk_classroom_id索引

四、总结

  1. 连接查询、子查询都有可能因为字符集不同导致查询不走索引。

  2. 不是字符集不同,连接查询、子查询就必定不走索引,还要看两字段字符集的子集、超集关系。

  3. 因为字符集导致连接查询、子查询不走索引时,可以尝试用convert()解决,但是是有风险的,需要注意convert的目标编码方式,是否可以存该字段可能存储的所有字符种类。

你可能感兴趣的:(MySQL 不相关子查询索引失效问题分析 - 字段编码方式不同导致)