MySQL表字段字符集不同导致的索引失效问题

转自:MySQL表字段字符集不同导致的索引失效问题

1. 概述

昨天在一位同学的MySQL机器上面发现了这样一个问题,MySQL两张表做left join时,执行计划里面显示有一张表使用了全表扫描,扫描全表近100万行记录,大并发的这样的SQL过来数据库变得几乎不可用了。MySQL版本为官方5.7.12。

2. 问题重现

首先,表结构和表记录如下:

mysql> show create table t1\G
*************** 1. row ***************
Table: t1
Create Table: 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
1 row in set (0.00 sec)mysql> show create table t2\G
*********** 1. row *******************
Table: t2
Create Table: 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
1 row in set (0.00 sec)mysql> select * from t1;
+—-+——+———————————-+
| id | name | code |
+—-+——+———————————-+
| 1 | aaaa | ...... |
| 2 | bbbb | ...... |
| 3 | cccc | ...... |
| 4 | dddd | ...... |
| 5 | eeee | ...... |
+—-+——+———————————-+
5 rows in set (0.00 sec)mysql> select * from t2;
+—-+——+———————————-+
| id | name | code |
+—-+——+———————————-+
| 1 | aaaa | ...... |
| 2 | bbbb | ...... |
| 3 | cccc | ...... |
| 4 | dddd | ...... |
| 5 | eeee | ...... |
+—-+——+———————————-+
5 rows in set (0.00 sec)

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

mysql> desc select * from t2 left join t1 on t1.code = t2.code where t2.name = 'dddd'\G
******************* 1. row ****************
id: 1
select_type: SIMPLE
table: t2
partitions: NULL
type: ref
possible_keys: idx_name
key: idx_name
key_len: 83
ref: const
rows: 1
filtered: 100.00
Extra: NULL
****************** 2. row **************
id: 1
select_type: SIMPLE
table: t1
partitions: NULL
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 5
filtered: 100.00
Extra: Using where; Using join buffer (Block Nested Loop)
2 rows in set, 1 warning (0.01 sec)

可以明显地看到,t2.name = ‘dddd’使用了索引,而t1.code = t2.code这个关联条件没有使用到t1.code上面的索引,一开始Scott也百思不得其解,但是机器不会骗人。Scott用show warnings查看改写后的执行计划如下:

mysql> show warnings;| Level | Code | Message |
| Note | 1003 | /* select#1 */ select `testdb`.`t2`.`id` AS `id`,`testdb`.`t2`.`name` AS `name`,`testdb`.`t2`.`code` AS `code`,`testdb`.`t1`.`id` AS `id`,`testdb`.`t1`.`name` AS `name`,`testdb`.`t1`.`code` AS `code` from `testdb`.`t2` left join `testdb`.`t1` on((convert(`testdb`.`t1`.`code` using utf8mb4) = `testdb`.`t2`.`code`)) where (`testdb`.`t2`.`name` = 'dddd') |1 row in set (0.00 sec)

在发现了convert(testdb.t1.code using utf8mb4)之后,Scott发现2个表的字符集不一样。t1为utf8,t2为utf8mb4。但是为什么表字符集不一样(实际是字段字符集不一样)就会导致t1全表扫描呢?下面来做分析。

  1. 首先t2 left join t1决定了t2是驱动表,这一步相当于执行了select * from t2 where t2.name = ‘dddd’,取出code字段的值,这里为’8a77a32a7e0825f7c8634226105c42e5’;

  2. 然后拿t2查到的code的值根据join条件去t1里面查找,这一步就相当于执行了select * from t1 where t1.code = ‘8a77a32a7e0825f7c8634226105c42e5’;

  3. 但是由于第(1)步里面t2表取出的code字段是utf8mb4字符集,而t1表里面的code是utf8字符集,这里需要做字符集转换,字符集转换遵循由小到大的原则,因为utf8mb4是utf8的超集,所以这里把utf8转换成utf8mb4,即把t1.code转换成utf8mb4字符集,转换了之后,由于t1.code上面的索引仍然是utf8字符集,所以这个索引就被执行计划忽略了,然后t1表只能选择全表扫描。更糟糕的是,如果t2筛选出来的记录不止1条,那么t1就会被全表扫描多次,性能之差可想而知。

3. 问题解决

既然原因已经清楚了,如何解决呢?当然是改字符集了,把t1改成和t2一样或者把t2改成t1都可以,这里选择把t1转成utf8mb4。那怎么转字符集呢?

有的同学会说用alter table t1 charset utf8mb4;但这是错的,这只是改了表的默认字符集,即新的字段才会使用utf8mb4,已经存在的字段仍然是utf8。

mysql> alter table t1 charset utf8mb4;
Query OK, 0 rows affected (0.01 sec)
Records: 0 Duplicates: 0 Warnings: 0mysql> show create table t1\G
************** 1. row ***************
Table: t1
Create Table: CREATE TABLE `t1` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(20) CHARACTER SET utf8 DEFAULT NULL,
`code` varchar(50) CHARACTER SET utf8 DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_code` (`code`),
KEY `idx_name` (`name`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4
1 row in set (0.00 sec)

只有用alter table t1 convert to charset utf8mb4;才是正确的。

但是还要注意一点,alter table 改字符集的操作是阻塞写的(用lock = node会报错)所以业务高峰时请不要操作,即使在业务低峰时期,大表的操作仍然建议使用pt-online-schema-change在线修改字符集。

mysql> alter table t1 convert to charset utf8mb4, lock=none;
ERROR 1846 (0A000): LOCK=NONE is not supported. Reason: Cannot change column type INPLACE. Try LOCK=SHARED.
mysql> alter table t1 convert to charset utf8mb4, lock=shared;
Query OK, 5 rows affected (0.04 sec)
Records: 5 Duplicates: 0 Warnings: 0mysql> show create table t1\G
******************** 1. row **************
Table: t1
Create Table: 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=utf8mb4
1 row in set (0.00 sec)

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

mysql> desc select * from t2 join t1 on t1.code = t2.code where t2.name = 'dddd'\G
******** 1. row ******************
id: 1
select_type: SIMPLE
table: t2
partitions: NULL
type: ref
possible_keys: idx_code,idx_name
key: idx_name
key_len: 83
ref: const
rows: 1
filtered: 100.00
Extra: Using where
********* 2. row *************
id: 1
select_type: SIMPLE
table: t1
partitions: NULL
type: ref
possible_keys: idx_code
key: idx_code
key_len: 203
ref: testdb.t2.code
rows: 1
filtered: 100.00
Extra: NULL
2 rows in set, 1 warning (0.00 sec)

4. 注意点

  1. 表字符集不同时,可能导致join的SQL使用不到索引,引起严重的性能问题;

  2. SQL上线前要做好SQL Review工作,尽量在和生产环境一样的环境下Review;

  3. 改字符集的alter table操作会阻塞写,尽量在业务低峰操作,建议用pt-online-schema-change;

  4. 表结构字符集要保持一致,发布时要做好审核工作;

  5. 如果要大批量修改表的字符集,同样做好SQL的Review工作,关联的表的字符集一起做修改。

5. 问题讨论

最后问一个问题,假设现在t1和t2表的字符集还未修改,如果上面那个问题SQL换成如下(即把t2 left join t1换成t1 left join t2),还会出现索引失效问题吗?为什么?

select * from t1 join t2 on t1.code = t2.code where t1.name = 'dddd'

MySQL表字段字符集不同导致的索引失效问题相关推荐

  1. mysql 各表charset不同_MySQL表字段字符集不同导致的索引失效问题

    1. 概述 昨天在一位同学的MySQL机器上面发现了这样一个问题,MySQL两张表做left join时,执行计划里面显示有一张表使用了全表扫描,扫描全表近100万行记录,大并发的这样的SQL过来数据 ...

  2. mysql字符集和表字符集_设置数据库的字符集和设置表字段字符集的区别是什么?...

    对于oracle来说,只有数据库字符集这个说法,不存在什么表字符集和字段字符集.你说的这个是mysql的字符集,数据库字符集可以和表字符集不同,也可以和列字符集不同,也就是说,你的数据库字符集为utf ...

  3. c mysql 的默认值_为mysql表字段设置默认值的方法及注意细节

    为mysql表字段设置默认值的方法及注意细节,有需要的朋友可以参考下. 环境:MySQL 5.1 + 命令行工具 问题:MySQL表字段设置默认值 解决方法: 代码如下: --SQL: CREATE ...

  4. MySQL表字段不能用describe

    MySQL表字段不能用describe 前言 今天写毕设,我的表有一个字段用来存放描述,于是就用了describe,我项目用了Spring Boot和Mybatis-Plus,当我新增数据时,发现控制 ...

  5. mysql 整型默认值,MySQL表字段设立默认值

    mysql表字段设置默认值 环境 MySQL 5.1 + 命令行工具 问题 MySQL表字段设置默认值 解决 --SQL: CREATE TABLE test( i_a int NOT NULL DE ...

  6. truncate分区表的操作,会导致全局索引失效?

    今天看到<删除分区如何不让全局索引失效?>这篇文章有朋友提了个问题, truncate是删除数据操作,但他是DDL语句,不是delete这种DML语句,不会写redo和undo,不能rol ...

  7. mysql 表字段信息从一张表迁移到另一张表_MySQL(数据库)笔记

    ###数据库 之前通过流去操作文件保存数据库的弊端: 1.执行效率低 2.开发成本高 3.一般只能保存小量数据 4.只能保存文本数据 ####什么是DB - DataBase 数据库:代表文件集合 # ...

  8. mysql 表字段信息从一张表迁移到另一张表_Mysql之分库分表

    互联网系统需要处理大量用户的请求.比如微信日活用户破10亿,海量的用户每天产生海量的数量:美团外卖,每天都是几千万的订单,那这些系统的用户表.订单表.交易流水表等是如何处理呢? 数据量只增不减,历史数 ...

  9. 修改表名_面试官:如何批量修改mysql表字段、表、数据库字符集和排序规则

    概述 目前数据库字符集统一用的utf8,由于项目需要,引进了表情,但是utf8mb5才支持表情字符,所以需统一修改数据库字符集,下面介绍批量修改数据库字符集的办法. 修正顺序是字段级别>表级别& ...

最新文章

  1. C++为什么空格无法输出_数据的输入输出举例
  2. elasticSearch6源码分析(12)DiscoveryModule
  3. A题 Go! Fun游长三角 2021年第一届长三角高校数学建模竞赛
  4. 如何使用命令行编译运行java文件(一个或多个),找不到主类解决
  5. 解决 锁定文件失败 打不开磁盘“D:\ubuntu\Ubuntu 64 位.vmdk”或它所依赖的某个快照磁盘。 模块 Disk”启动失败...
  6. Stomp-Spring服务器端的Web套接字Java客户端
  7. 中国首辆火星车全球征名,李彦宏推荐命名:祝融
  8. 零基础(转行,应届生、在校生)前端开发学习计划
  9. vscode中控制台不能输入_vscode控制台不能输入怎么办
  10. 单层感知器->BP算法详细推导
  11. 高级java技术栈图
  12. python 8 字典
  13. 2022百度春节红包活动 集好运分5亿活动规则
  14. 山水之道第四境——精灵的飞天之舞
  15. Twitter将开通直播打赏功能 帮助美国网红赚钱
  16. 微信小程序开发报错:page is not constructed because it is not found
  17. 远程桌面全屏无法退出
  18. 以用户的角度看待应用推广渠道
  19. 基于图像的虚拟试衣:CloTH-VTON:Clothing Three-dimensional reconstruction for Hybrid image-based Virtual Try-On
  20. 美团的护城河与王兴的星辰大海

热门文章

  1. java 判断是不是奇数_很火的Java题——判断一个整数是否是奇数
  2. 课设Day4—风雨之后见彩虹
  3. 在Android应用中使用Https自定义证书,CER转BKS
  4. 工业机器人的10大应用领域
  5. 资源分享 - 网络安全课件
  6. 活动明天见 | DataFunSummit 2022 AI基础软件架构峰会圆桌会
  7. Paimei(白眉)安装
  8. ADF CUSTOMIZATION PART 2: PERSONALIZATION
  9. Facebook Live 诞生的背后故事
  10. python挂机学习,自动处理弹窗