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类型为例来介绍,其他类型可能稍有不同):
  1. 字符的编码:table_a的编码是utf8mb4,vharchar类型每个字符占4个字节长度,10*4=40(参考官网)
  2. 字段是否可以为null: 字段b是允许为null的,因此实际长度40+1=41(参考官网)
  3. 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 |
+----+-------------+---------+------------+------+---------------+------+---------+-------+------+----------+-----------------------+
posted @ 2025-05-27 12:12  纳兰小依  阅读(27)  评论(0)    收藏  举报