key_len 列值是什么

explain 关键字列出的执行计划中有一个列为 key_lenMySQL 官方文档对其作出的解释为:

The key_len column indicates the length of the key that MySQL decided to use. The value of key_len enables you to determine how many parts of a multiple-part key MySQL actually uses. If the key column says NULL, the key_len column also says NULL.

Due to the key storage format, the key length is one greater for a column that can be NULL than for a NOT NULL column.

意思就是说,key_len 列表明了 MySQL 决定要使用的索引的长度。它的值让你能够确定 MySQL 实际使用了多部分索引中的哪些部分。

这里的 multiple-part 应该指的是多个字段组合成的索引。

有时候因为最左前缀原则,导致只有部分索引被使用。这个时候就可以通过 key_len 的长度来判断到底走了哪些列的索引。

如何计算

那这个列的值是怎么被计算出来的呢?官网并没有找到详细的解释。

经过 Google 查询之后,找到一些网友分享的经验,在此做个记录。

在计算 key_len 时,需要考虑以下几个点:

  • 索引字段的数据类型是变长还是定长;
  • 当索引字段为定长数据类型时,如 charintdatetime,需要有是否为空的标记,这个标记占用 1 个字节(对于 NOT NULL 来说不需要这 1 个字节);
  • 当索引字段为变长数据类型时,如 varchar,除了是否为空的标记外,还需要有长度信息,需要占用 2 个字节;
  • 对于 charvarcharblobtext 等,key_len 的长度还和字符集有关,latin1 一个字符占用 1 个字节,gbk 一个字符占用 2 个字节,utf8 一个字符占用 3 个字节.

一些常见的数据类型具体的计算公式总结如下:

对于变长字段:

  • varchar(X) 变长字段且 允许为 NULLX * (字符集长度: latin1=1, gbk=2, utf8mb4=4) + 1(NULL值标记) + 2(变长字段长度信息)字节
  • varchar(X) 变长字段且 不允许为 NULLX * (字符集长度: latin1=1, gbk=2, utf8mb4=4) + 2(变长字段长度信息)字节

对于定长字段:

  • char(X) 定长字段且 允许为 NULLX * (字符集长度: latin1=1, gbk=2, utfmb4=4) + 1(NULL值标记) 字节
  • char(X) 定长字段且 不允许为 NULLX * (字符集长度: latin1=1, gbk=2, utf8mb4=4) 字节
  • int 定长字段且 允许为 NULL4 字节
  • int 定长字段且 不允许为 NULL4 + 1 字节
  • typeint 定长字段且 允许为 NULL1 字节
  • typeint 定长字段且 不允许为 NULL1 + 1 字节
  • date 定长字段且 允许为 NULL3 个字节
  • date 定长字段且 不允许为 NULL3 + 1 个字节
  • datetime 定长字段且 允许为 NULL5 个字节
  • datetime 定长字段且 不允许为 NULL5 + 1 个字节

实践

utf8mb4 字符集为例进行测试,其他字符集可以使用同样的方法进行测试。

创建表

CREATE TABLE `test` (
  `id` int NOT NULL,
  `name` varchar(20) NOT NULL COMMENT '姓名',
  `sex` tinyint NOT NULL COMMENT '性别,1:男,2:女',
  `email` varchar(20) DEFAULT NULL,
  `age` tinyint default 0,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

创建索引

CREATE INDEX NAME_IDX USING BTREE ON test (name);
CREATE INDEX SEX_IDX USING BTREE ON test (sex);
CREATE INDEX EMAIL_IDX USING BTREE ON test (email);
CREATE INDEX AGE_IDX USING BTREE ON test (age);
CREATE INDEX name_age_sex_IDX USING BTREE ON test (name,age,sex);

插入测试数据

INSERT INTO test (id,name,sex,email,age) VALUES
	 (1,'tom',1,'[email protected]',16),
	 (2,'lucy',2,'[email protected]',18),
	 (3,'nancy',2,'[email protected]',22),
	 (4,'lilly',2,'[email protected]',25),
	 (5,'letter',1,'[email protected]',35);

EXPLAIN 分析

1、根据主键 id 分析,由于 id 为 int 类型,设置的 NOT NULL ,key_len 为 4

从下面的 explain 执行计划可以看出,key_len 确实为 4。

mysql> explain select * from test where id = 1;
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type  | possible_keys | key     | key_len | ref   | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
|  1 | SIMPLE      | test3 | NULL       | const | PRIMARY       | PRIMARY | 4       | const |    1 |   100.00 | NULL  |
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+

2、根据 email 分析,由于 email 为 varchar 类型,长度为 20,默认为 NULL,因此 key_len 为 20*4+2+1=83

从下面的 explain 执行计划可以看出,key_len 确实为 83。

mysql> explain select * from test where email like 'tom%';
+----+-------------+-------+------------+-------+---------------+-----------+---------+------+------+----------+-----------------------+
| id | select_type | table | partitions | type  | possible_keys | key       | key_len | ref  | rows | filtered | Extra                 |
+----+-------------+-------+------------+-------+---------------+-----------+---------+------+------+----------+-----------------------+
|  1 | SIMPLE      | test  | NULL       | range | email_IDX     | email_IDX | 83      | NULL |    1 |   100.00 | Using index condition |
+----+-------------+-------+------------+-------+---------------+-----------+---------+------+------+----------+-----------------------+

3、根据 sex 分析,由于 sex 为 tinyint 类型,设置为 NOT NULL,因此 key_len 为 1

从下面的 explain 执行计划可以看出,key_len 确实为 1。

mysql> explain select * from test where sex = 1;
+----+-------------+-------+------------+------+---------------+---------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key     | key_len | ref   | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+---------+---------+-------+------+----------+-------+
|  1 | SIMPLE      | test  | NULL       | ref  | sex_IDX       | sex_IDX | 1       | const |    1 |   100.00 | NULL  |
+----+-------------+-------+------------+------+---------------+---------+---------+-------+------+----------+-------+

4、测试复合索引

前提:将单个字段的索引去除后进行测试

ALTER TABLE test DROP INDEX age_IDX;
ALTER TABLE test DROP INDEX name_IDX;
ALTER TABLE test DROP INDEX sex_IDX;

4.1、根据 name 和 age 分析,name 的索引长度为 20*4+2=82, age 的索引长度为 1+1=2,因此 key_len 长度为 84

从下面的 explain 执行计划可以看出,当只用到 name_age_sex_IDX 索引中的 name 和 age 两个 key_len 确实为 84

mysql> explain select * from test where name like 'l%' and age > 20 and sex = 2;
+----+-------------+-------+------------+-------+------------------+------------------+---------+------+------+----------+-----------------------+
| id | select_type | table | partitions | type  | possible_keys    | key              | key_len | ref  | rows | filtered | Extra                 |
+----+-------------+-------+------------+-------+------------------+------------------+---------+------+------+----------+-----------------------+
|  1 | SIMPLE      | test  | NULL       | range | name_age_sex_IDX | name_age_sex_IDX | 84      | NULL |    3 |    20.00 | Using index condition |
+----+-------------+-------+------------+-------+------------------+------------------+---------+------+------+----------+-----------------------+

4.2、在 4.1 的基础上,修改一下 SQL 语句,将 age > 20 改成 age >= 20,这样就能用上三个字段的联合索引,此时,key_len 应该为 85

从下面的 explain 执行计划可以看出,当完全用到 name_age_sex_IDX 索引时, key_len 确实为 85

mysql> explain select * from test where name like 'l%' and age >= 20 and sex = 2;
+----+-------------+-------+------------+-------+------------------+------------------+---------+------+------+----------+-----------------------+
| id | select_type | table | partitions | type  | possible_keys    | key              | key_len | ref  | rows | filtered | Extra                 |
+----+-------------+-------+------------+-------+------------------+------------------+---------+------+------+----------+-----------------------+
|  1 | SIMPLE      | test  | NULL       | range | name_age_sex_IDX | name_age_sex_IDX | 85      | NULL |    3 |    20.00 | Using index condition |
+----+-------------+-------+------------+-------+------------------+------------------+---------+------+------+----------+-----------------------+

总结

通过了解 key_len 的计算规则, 就可更加清楚 MySQL 在决定复合索引时具体使用了哪些索引,从而更好的为进行索引优化提供依据。