← 数据库 · MySQL

MySQL 索引设计:从一条慢查询读懂 EXPLAIN

以事件明细查询为例,连接查询形态、联合索引、实际扫描量和写入成本,建立可验证的索引优化过程。

Read in English →

工程学习与设计笔记。示例使用合成场景;适用版本与参考资料见正文。

适用范围:MySQL 8.0 / InnoDB;EXPLAIN ANALYZE 需要 8.0.18 及以上。本文使用独立构造的示例,未引用线上数据或性能结论。

慢查询优化最容易走偏的一步,是看到 type=ALL 就加索引,看到 key 非空就宣布优化完成。真正要回答的是:为了返回这几十行,数据库读了多少数据、做了多少排序,又给写入增加了多少负担。

先把查询需求说完整

假设有一张广告事件明细表,后台需要查看某账户在指定时间之后的最近 50 条点击记录。为了让同一时间戳的记录顺序稳定,按 event_time、id 双字段排序:

CREATE TABLE event_log (
  id BIGINT NOT NULL PRIMARY KEY,
  account_id BIGINT NOT NULL,
  event_type VARCHAR(16) NOT NULL,
  event_time DATETIME(3) NOT NULL,
  payload JSON,
  KEY idx_account_type_time (account_id, event_type, event_time, id)
) ENGINE=InnoDB;

SELECT id, event_time
FROM event_log
WHERE account_id = 42
  AND event_type = 'click'
  AND event_time >= '2026-01-01 00:00:00'
ORDER BY event_time DESC, id DESC
LIMIT 50;

这里的索引顺序是一项待验证的设计:先固定账户与类型,再沿时间范围查找。联合索引能服务哪些查询,首先取决于其列顺序和查询约束;不能把它理解为几个单列索引的简单集合。关于左前缀的准确规则见 MySQL 联合索引文档 。

这个设计不意味着所有查询都该共用它。如果另一个接口只按 event_type 查全站数据,就需要单独分析。反过来,若账户内几乎全是点击,event_type 的过滤收益可能很小;是否保留它,还要看其他查询、索引宽度与写入频率。

看执行计划时按问题读

先执行普通 EXPLAIN,确认访问路径和预估扫描量;再在可控环境执行:

EXPLAIN ANALYZE
SELECT id, event_time
FROM event_log
WHERE account_id = 42
  AND event_type = 'click'
  AND event_time >= '2026-01-01 00:00:00'
ORDER BY event_time DESC, id DESC
LIMIT 50;

EXPLAIN ANALYZE 会真正运行语句,给出实际行数、耗时与循环次数,而不是只做估算。嵌套循环的内层只看单次耗时会低估总工作量;也不能把父子节点耗时机械相加,因为父节点包含子节点工作。版本与输出语义见 EXPLAIN 官方说明 。

我会沿执行树检查四个问题:索引把候选集缩到了多大?过滤掉了多少候选?排序发生在多少行之上?LIMIT 是否足够早地结束读取?如果预估与实际相差很大,先检查参数分布、统计信息与数据倾斜,再考虑提示优化器。

Using filesort 表示额外排序步骤,并不直接等于“排序写磁盘”;是否利用索引排序也受查询形态影响。应结合候选行数与实际时间判断代价,参见 ORDER BY 优化文档 。

优化要覆盖真实分布

验证不能只拿一个普通账户跑一次。至少准备大账户、小账户、无结果账户,以及窄时间窗口和宽时间窗口。缓存已热时的偶然快,不能代表冷启动,也不能代表并发读取。

如果页面还要返回 payload,不要为了“覆盖索引”把所有字段塞进索引。可以先比较直接回表与“取 50 个主键后再取详情”两种方案,但后者多一次查询,也引入两次查询间的数据变化问题。只有观测到回表是主要成本,才值得增加复杂度。

深分页同样需要单独设计。OFFSET 100000 让数据库跳过大量结果;游标分页可以传回最后一条的时间与 ID,用严格的小于条件继续读取。其前提是排序键稳定,且产品接受分页期间新增、删除带来的可见性语义。

一次可交付的优化应留下什么

留下原始 SQL、参数样本、表与索引定义、优化前后计划,以及同一负载下的扫描量和延迟分位数。再补上写入吞吐、索引空间、复制延迟的变化,避免把读性能收益转嫁成写链路问题。

上线后按查询摘要观察,而不是只盯一条手工 SQL。若新索引收益不稳定,保留回退路径,并解释是数据分布变了、计划变了,还是锁等待和 I/O 才是真正瓶颈。索引优化的终点,是一个被数据支持的访问路径选择。

References

← 继续阅读专题回到顶部 ↑