MySQL 索引失效与 EXPLAIN 查询计划

“我建了索引,查询为什么还是慢?”索引是否被使用取决于查询条件、数据分布和优化器估算。判断问题时,不应只背“索引失效清单”,而要看执行计划,并用代表性数据验证。

1. 准备一个示例

1
2
3
4
5
6
7
8
CREATE TABLE orders (
id BIGINT PRIMARY KEY,
user_id BIGINT NOT NULL,
status TINYINT NOT NULL,
created_at DATETIME NOT NULL,
amount DECIMAL(10, 2) NOT NULL,
INDEX idx_user_status_time (user_id, status, created_at)
);

联合索引按列顺序组织。(user_id, status, created_at) 一般可用于从最左列开始的前缀条件,例如只按 user_id 查,或按 user_id 和 status 查。跳过最左列后,不能简单认为后续列仍可像独立索引一样定位。

2. 先用 EXPLAIN 看计划

1
2
3
4
5
EXPLAIN
SELECT id, created_at
FROM orders
WHERE user_id = 42 AND status = 1
ORDER BY created_at;

重点观察访问类型、候选索引、实际选择的 key、估算扫描行数和额外排序信息。字段含义会随输出格式和 MySQL 版本有所不同;EXPLAIN FORMAT=TREE 可呈现树形计划,MySQL 支持时还可用 EXPLAIN ANALYZE 对查询进行实际执行并显示迭代器统计。后者会运行查询,应在适当环境和数据范围内使用。

3. 常见的索引使用问题

跳过联合索引的最左列

索引是 (user_id, status, created_at),只按 status 查询时,通常不能利用该索引快速定位到一段连续范围。应根据真实查询模式设计索引,而不是机械地把所有列放在一个联合索引中。

对索引列做表达式计算

1
2
-- 可能使普通索引难以直接定位
WHERE DATE(created_at) = '2026-10-01'

可改为范围条件:

1
2
WHERE created_at >= '2026-10-01 00:00:00'
AND created_at < '2026-10-02 00:00:00'

隐式类型转换或不匹配的比较方式

比较的列类型和参数类型不一致时,转换方式可能影响索引使用。应让参数类型与列定义匹配,并检查字符集、排序规则和实际计划。

低选择性和成本估算

某列只有少数几种值时,索引可能筛不掉多少记录;优化器可能判断扫描表更便宜。索引存在不代表每次都会被选中,也不代表选中后一定更快。

4. 一个可靠的分析流程

  1. 用具有代表性的数据和参数重现慢查询。
  2. 看 EXPLAIN 的访问路径和估算行数。
  3. 检查索引列顺序、过滤条件、排序和返回列。
  4. 必要时使用实际执行计划或慢查询信息核实估算。
  5. 调整 SQL 或索引后,在相同数据规模下比较结果。
  6. 评估写入成本、索引空间和其他查询的影响。

不要为了让某个查询显示“用上索引”而盲目添加索引;索引会增加写入和存储成本。计划是解释优化器的选择,最终仍要结合真实负载测量。

参考:MySQL 8.4 EXPLAIN 文档。