前言
❶本讲基于MySQL8.0版本,对其他版本,尤其是5.7之前可能不太适用;
❷通过其他渠道获取到的知识,它最多叫作者的观点,我们持一种怀疑态度,并想办法自己去求证。
以下示例基于这几张表
表demo_char_code(1条数据)
CREATE TABLE `demo_char_code` (
`id` int NOT NULL AUTO_INCREMENT ,
`a` varchar(2) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT 'utf8编码' ,
`b` varchar(2) CHARACTER SET utf32 COLLATE utf32_general_ci NULL DEFAULT NULL COMMENT 'utf32编码' ,
`c` varchar(2) CHARACTER SET gbk COLLATE gbk_chinese_ci NULL DEFAULT NULL COMMENT 'gbk编码' ,
PRIMARY KEY (`id`)
)
ENGINE=InnoDB
DEFAULT CHARACTER SET=utf8 COLLATE=utf8_general_ci
COMMENT='字符编码演示'
AUTO_INCREMENT=1
ROW_FORMAT=DYNAMIC;
表demo_test(640条数据)
CREATE TABLE `demo_test` (
`id` int NOT NULL AUTO_INCREMENT ,
`a` int NULL DEFAULT NULL ,
`b` int NULL DEFAULT NULL ,
`c` varchar(8) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL ,
`org_usc_code_notidx` varchar(18) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL ,
`org_usc_code` varchar(18) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL ,
`c_utf8mb4` varchar(18) CHARACTER SET utf8mb4 COLLATE utf8mb4_vietnamese_ci NULL DEFAULT NULL ,
PRIMARY KEY (`id`),
INDEX `idx_a` (`a`) USING BTREE ,
INDEX `idx_c` (`c`) USING BTREE ,
INDEX `idx_org_usc_code` (`org_usc_code`) USING BTREE ,
INDEX `idx_c_utf8mb4` (`c_utf8mb4`) USING BTREE
)
ENGINE=InnoDB
DEFAULT CHARACTER SET=utf8 COLLATE=utf8_general_ci
AUTO_INCREMENT=11
ROW_FORMAT=DYNAMIC;
表demo_test_ass(3条数据)
CREATE TABLE `demo_test_ass` (
`rid` int NOT NULL AUTO_INCREMENT ,
`c1` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_vietnamese_ci NULL DEFAULT NULL ,
`c2` varchar(16) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL ,
`c3` int NULL DEFAULT NULL ,
`create_date` timestamp NULL DEFAULT NULL ,
PRIMARY KEY (`rid`),
INDEX `idx_create_date` (`create_date`) USING BTREE
)
ENGINE=InnoDB
DEFAULT CHARACTER SET=utf8 COLLATE=utf8_general_ci
AUTO_INCREMENT=202113
ROW_FORMAT=DYNAMIC;
一. 有趣的MySQL现象
1.1中文长度不一致--有点意思

tip: 1. 同一字符在不同编码占用的字节长度可能不一样(比如中文);
2. 一个中文在utf8(或其超集)占用3个字节,utf32编码占用4个字节,gbk编码占用2个字节;
3. MySQL varchar类型长度定义的是字符长度而不是字节长度;
1.2 可以走索引,亦可以不走索引--令人费解
我们可以通过optimizer_trace进行优化器跟踪,来了解为什么有时候走索引有时候又不走索引
SET optimizer_trace="enabled=on";
select * from demo_test where a > 50;
SELECT * FROM information_schema.OPTIMIZER_TRACE;
select * from demo_test where a > 500;
SELECT * FROM information_schema.OPTIMIZER_TRACE;
SET optimizer_trace="enabled=off";
下面是条件a>50的优化器跟踪,发现通过走索引的成本要远远大于全表扫描,因此走全表扫描。
tip: 1.EXPLAIN 显示选定的计划,但Optimizer Trace 能显示为什么选择计划;
2.MySQL 会使用一个基于成本(cost)的优化器对执行计划进行选择;
1.3 可以查出一条数据--不可思议

tip: 1. 类型不同进行运算时会隐私转换;
2. 字符串转数字规则:从字符串第一个字符开始转换,如果可以转成数字则继续下一个字符转换;若无法转成数字则结束【如果是第一个字符无法转换则转成0】; 如: '1a'-->1, ''-->0, 'ab'-->0, 'c3d'-->0, '123d'-->123
1.4 它是怎么转换的--一探究竟
上面的转换规则究竟是:左边转右边类型或右边转左边类型?还是数字转字符串类型或字符串转数字类型?我们可以进行以下验证:
tip: 字符串与数字进行比较时,会将字符串尝试转成数字;
二. 令人疑惑的误区
2.1 索引字段上发生隐式转换会不会走索引?
/*
隐式字符编码转换、隐式类型转换、条件字段函数操作
EXPLAIN select test.* from demo_test test, demo_test_ass t where test.c = t.c1 and t.rid = 1; -- 编码不一致,索引失效
EXPLAIN select test.* from demo_test test, demo_test_ass t where test.c = t.c2 and t.rid = 1; -- 无需类型转换
EXPLAIN select test.* from demo_test test, demo_test_ass t where test.c = t.c3 and t.rid = 1; -- 类型不一致,索引失效
EXPLAIN select test.* from demo_test test, demo_test_ass t where test.c_utf8mb4 = t.c2 and t.rid > 1; -- 编码不一致,被驱动表字符转换,索引有效
EXPLAIN select test.* from demo_test test, demo_test_ass t where test.c_utf8mb4 = t.c2 and test.id = 1; -- 编码不一致,驱动表字符转换,索引失效
*/
tip: 1. 对索引字段做函数操作,可能会破坏索引值的有序性,因此优化器就决定放弃走树搜索功能;
2. utf8编码与utf8mb4编码进行比较时,会将utf8编码自动隐式转成utf8mb4编码;
2.2 建了索引查询反而更慢,或者说删掉索引查询更快,存在吗?
/*
*EXPLAIN select * from demo_company_info where (rid_1 between 1 and 2000) and (rid_2 between 50000 and 150000) ORDER BY rid_2 limit 1; //rid_1与rid_2都有索引,查询慢
*EXPLAIN select * from demo_company_info where (rid_1 between 1 and 2000) and (rid_3 between 50000 and 150000) ORDER BY rid_3 limit 1; //rid_3无索引,查询快
*EXPLAIN select * from demo_company_info force index(idx_rid_1) where (rid_1 between 1 and 2000) and (rid_2 between 50000 and 150000) ORDER BY rid_2 limit 1; //强制走指定索引,查询快
*/
/*tip: 大多数时候优化器都能找到正确的索引,但是有些情况mysql选错索引。我们可以采用force index强行选择一个索引或者可以考虑修改语句,引导MySQL使用我们期望的索引。
*/
2.3 如果一个用户表上亿级别的表有5个字段(id,use_name姓名, id_card身份证号,addr出生地址,age年龄),根据身份证号查姓名,只能给id_card建索引才能实现快速查找?
/*tip: 1. 第使用倒序存储(函数reverse)。存储身份证号的时候把它倒过来存,然后用其前6位建索引。索引长度变小,查询效率更高;
2. 使用hash字段(函数crc32)。可以在表上再创建一个整数字段,来保存身份证的校验码(4个字节),同时在这个字段上创建索引。索引长度变小,查询效率更高;
*/
三. FAQ互动问答
3.1 mysql表顺序、条件顺序、运算符左右位置无关,mysql自动会把小表当成驱动表,大表作为被驱动表;
在决定哪个表做驱动表的时候,应该是两个表按照各自的条件过滤,过滤完成之后,计算参与join的各个字段的总数据量,数据量小的那个表,就是“小表”,作为驱动表。
3.2 mysql8 in效率与exists效率一样。可见MySQL5.7和8.0中in和exists关键字_荒野大码农的博客-CSDN博客
由于本人没有在5.7试,只在mysql8测试效率差不多,只把上文结论贴出来
转存失败重新上传取消
本次分享内容素材来源于
1. 丁奇《Mysql实战45讲》
2. SH的全栈笔记:浅入浅出 MySQL 索引 - 知乎