key_len 是什么
key_len 是 EXPLAIN 输出里「本次索引查找实际使用的字节数」,对照列定义的宽度就能反推出复合索引用到了前几列。它验证的是最左前缀到底走了多深:key_len 只覆盖第 1 列,说明后面的列没有参与索引查找,最多只能在回表后作为过滤条件。计算宽度时要考虑类型长度、字符集、是否允许 NULL,以及 VARCHAR 的长度前缀。
基本信息
| 适用平台 | MySQL 8.0(PostgreSQL 没有 key_len,改看索引条件与 BUFFERS 输出) |
|---|---|
| 常见宽度 | INT 4 字节、BIGINT 8、DATE 3、DATETIME 5(不含小数秒) |
| 字符列 | utf8mb4 按 4 字节/字符计入,不是 3 字节 |
| 可空列 | 额外增加 1 字节 NULL 标志位 |
| VARCHAR | 额外增加 1-2 字节长度前缀,按声明的字节上限决定 |
| 用途 | 反推复合索引实际命中了前几列 |
典型使用场景
- 验证复合索引是否真的用上了第 2、3 列
- 排查「索引建了却只用了一列」的原因
- 确认 ORDER BY 的列是否参与了索引查找
- 对比改写前后索引前缀使用情况的变化
- 定位隐式类型转换导致的 key_len 异常
反推命中的列数
反推命中的列数
ALTER TABLE orders ADD INDEX idx_user_status_created (user_id, status, created_at);
EXPLAIN SELECT * FROM orders WHERE user_id = 100 AND status = 'paid';
# key: idx_user_status_created
# key_len 对应 user_id 与 status 两列宽度:第 3 列 created_at 未参与索引查找
EXPLAIN SELECT * FROM orders WHERE user_id = 100;
# key_len 只覆盖 user_id:仅用到第 1 列
EXPLAIN SELECT * FROM orders WHERE user_id = 100 AND created_at > '2026-01-01';
# 跳过 status 直接按 created_at 过滤时,created_at 无法用于索引查找宽度速查
| 列类型 | 字节宽度 | 备注 |
|---|---|---|
| TINYINT / SMALLINT | 1 / 2 | 整数类型宽度固定 |
| INT / BIGINT | 4 / 8 | 最常用的两类 |
| DATE / DATETIME | 3 / 5 | DATETIME 不含小数秒,带精度会额外增加 |
| VARCHAR(n) utf8mb4 | 4n + 1 或 2 | 加长度前缀 1-2 字节 |
| 可空列 | 额外 +1 | NULL 标志位 |
| DECIMAL(10,2) | 5 | 每 9 位十进制占 4 字节,另有符号与小数开销 |
key_len 只说明索引查找用了几列,过滤效果还要看 Extra 与 rows 估算:只用到前导列不等于查询一定慢,如果该列过滤后行数已经很少,剩余条件作为回表过滤也完全可行。
按 UTF-8 每字符 3 字节估算会得出错误结论:MySQL 的 utf8mb4 按 4 字节/字符计入 key_len,算错就会误判索引的使用情况。
记录基线很有用:把关键查询的 key、key_len、rows 抄进变更记录,下次改索引或升级版本后对比,能立刻看出是索引使用范围变了还是数据分布变了。