联合索引与 EXPLAIN ANALYZE
可运行 SQL
sql
USE mysql_course;
EXPLAIN FORMAT=TREE
SELECT order_id, total_amount
FROM sales_order
WHERE customer_id = 1
AND status = 'paid'
ORDER BY created_at DESC, order_id DESC
LIMIT 20;
CREATE INDEX idx_order_customer_status_created
ON sales_order(customer_id, status, created_at DESC, order_id DESC);
EXPLAIN ANALYZE
SELECT order_id, total_amount
FROM sales_order
WHERE customer_id = 1
AND status = 'paid'
ORDER BY created_at DESC, order_id DESC
LIMIT 20;
ALTER TABLE sales_order
ALTER INDEX idx_order_customer_status_created INVISIBLE;实验结束可将索引改回 VISIBLE 或删除。
机制解释
InnoDB 聚簇索引叶子页保存整行。二级索引按索引列排列并携带主键;查询其他列可能回表。联合索引顺序要同时服务等值条件、范围、排序和选择列,不能只按单列选择性排序。
EXPLAIN 的估算行数来自统计信息;EXPLAIN ANALYZE 提供执行后的实际循环、行数和时间。两者差距大时,应先检查数据分布、谓词相关性和统计信息。
不可见索引让优化器默认忽略索引但继续维护它,适合评估删除风险;它仍消耗写入和空间。
真实边界
- 小表全扫可能比走索引合理。
- 低选择性索引并非永远无效,联合索引和覆盖场景仍可能有价值。
- 索引增加 DML、页分裂、buffer pool 和备份成本。
- 示例数据太少不能证明生产计划,应使用脱敏后的分布或合成等价分布。
练习
- 比较只索引 customer_id 与联合索引的计划。
- 将查询改成 SELECT *,观察覆盖性和回表差异。
- 让索引不可见后验证计划,再说明安全删除还需要哪些证据。
验收清单
- 保存建索引前后 EXPLAIN ANALYZE 的实际 rows、loops 和时间。
- 能解释联合索引列序如何服务过滤与排序。
- 索引删除方案包含不可见观察期、写入成本和回滚。
深度加餐:索引是写入成本换读取证据
先写查询谓词、排序和返回列,再设计索引;不要把每个字段都单独建一棵树。
先做一个可观察实验
sql
CREATE INDEX idx_orders_status_time
ON orders(status, placed_at DESC, id DESC);
EXPLAIN ANALYZE
SELECT id, order_no, placed_at
FROM orders
WHERE status = 'PENDING'
ORDER BY placed_at DESC, id DESC
LIMIT 20;预期:计划应显示使用联合索引并减少回表/排序;如果 rows 仍很大,检查选择性与统计信息而不是继续堆索引。
原理下潜与工程边界
B+Tree 索引按左前缀组织;覆盖索引可直接返回所需列;EXPLAIN 的 estimated rows 与 ANALYZE 的实际耗时/行数可以暴露估算偏差。
函数包裹列、隐式类型转换、前导通配符会让索引难以使用;过宽索引增加写放大与 buffer pool 压力;DESC 语法与实际计划要以版本为准。
反例排错路线
保存变更前后的 EXPLAIN JSON、rows_examined、p95 延迟和写入吞吐;用 invisible index 先做撤销演练。
练习加码(仍不提供答案)
加码:为“租户 + 状态 + 时间”查询设计索引,比较左前缀不同排列在三种数据分布下的计划。
验收时请补充:一份可复现的环境/数据前提、正常结果、失败结果以及可回滚或清理动作。
继续查阅:MySQL 8.4 参考手册(外部资料已收录于证据账本) · InnoDB 文档(外部资料已收录于证据账本)