Mysql varchar字段的key_len如何计算
本文主要是分析在使用explain查看执行计划时,key_len是如何计算的。
问题背景
有一张表,其定义如下:
CREATE TABLE table_a (
id int NOT NULL,
b varchar(10) DEFAULT NULL,
c varchar(10) NOT NULL DEFAULT '1234567890',
PRIMARY KEY (id),
KEY b (b),
KEY c (c)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
表中的数据比较简单,只有有限的几条:
mysql> select * from table_a;
+----+------------+------------+
| id | b | c |
+----+------------+------------+
| 1 | 1234567890 | 1234567890 |
| 2 | 1234567890 | 1234567890 |
| 3 | 1234567890 | 1234567890 |
| 4 | 1234567890 | 1234567890 |
| 5 | 123456789 | 1234567890 |
+----+------------+------------+
隐式转换
最开始,我只是想验证查询的时候是否会做隐式转换,因此执行了如下的语句:explain select * from table_a where b = '1234567890abcd';
对应的输出为:
+----+-------------+---------+------------+------+---------------+------+---------+-------+------+----------+-----------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+---------+------------+------+---------------+------+---------+-------+------+----------+-----------------------+
| 1 | SIMPLE | table_a | NULL | ref | b | b | 43 | const | 4 | 100.00 | Using index condition |
+----+-------------+---------+------------+------+---------------+------+---------+-------+------+----------+-----------------------+
由于字段b的长度定义是10,输入的查询条件长度是14,明显是查不到数据的,但是extra里的Using index condition显示用到了ICP机制,因此这条查询语句并没有直接根据字段长度进行判断,而是查询了索引b,而且查询之后的结果不满足b='1234567890abcd'的条件,存储引擎根据该条件直接过滤掉了数据,最终给server层的数据集是空的。
具体的执行逻辑是:
- 对字符串1234567890abcd进行截断,只取前10个字符1234567890;
- 走b索引查满足b=1234567890的数据,那么这里可以查出来4条记录
- 根据查出来的记录中的b字段的值,与1234567890abcd对比,发现都不满足查询条件,所以将记录都多虑掉
- 返回空集给server层。
因此,上面的查询语句实际上等同于:
mysql> explain select * from table_a where b = concat('1234567890abcd',10);
+----+-------------+---------+------------+------+---------------+------+---------+-------+------+----------+-----------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+---------+------------+------+---------------+------+---------+-------+------+----------+-----------------------+
| 1 | SIMPLE | table_a | NULL | ref | b | b | 43 | const | 4 | 100.00 | Using index condition |
+----+-------------+---------+------------+------+---------------+------+---------+-------+------+----------+-----------------------+
key_len是怎么计算的
上面输出中的key_len引起了我的注意,凭直觉这里应该是个10的整数倍,但这里是43。经过分析,影响key_len结果的有以下几个因素(这里仅以varchar类型为例来介绍,其他类型可能稍有不同):- 字符的编码:table_a的编码是utf8mb4,vharchar类型每个字符占4个字节长度,10*4=40(参考官网)
- 字段是否可以为null: 字段b是允许为null的,因此实际长度40+1=41(参考官网)
- varchar类型需要额外2个字节记录字符长度: 最终结果是41+2=43
稍微有点费解的是第3条。在MySQL底层存储里,每一个varchar字段都会有一个1-2字节的空间来记录字符串实际的长度,如果字符串实际长度在255字节以内(小于等于),该空间占1个字节,否则占2个字节。从key_len的输出来看,MySQL是不考虑字段的定义,直接按最大2字节来算的。
上面的3条影响因素,前两条都可以在官网找到对应的描述,第3条没找到官方的说法,估计是写在了explain方法里。
可以用字段c来验证第2条:
mysql> explain select * from table_a where c = '1234567890abcd';
+----+-------------+---------+------------+------+---------------+------+---------+-------+------+----------+-----------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+---------+------------+------+---------------+------+---------+-------+------+----------+-----------------------+
| 1 | SIMPLE | table_a | NULL | ref | c | c | 42 | const | 5 | 100.00 | Using index condition |
+----+-------------+---------+------------+------+---------------+------+---------+-------+------+----------+-----------------------+

浙公网安备 33010602011771号