MySQL EXPLAIN 怎么看 从执行计划判断SaaS查询是否存在性能问题
在SaaS系统中,一条SQL可能每天执行几百万次。即使单次查询只增加几毫秒,累计起来也可能成为数据库CPU、I/O和连接池的长期负担。MySQL提供的EXPLAIN,是开发人员分析SQL执行计划最重要的工具之一。
但EXPLAIN并不是一张简单的“红绿灯表”。看到ALL、Using filesort或者key=NULL就立即加索引,往往会把问题越改越复杂。真正有效的优化方法,是理解优化器为什么选择这种执行方式,再结合实际数据量和运行时间判断它是否合理。
一、先看type,但不要把它当成绝对排名
type描述MySQL访问表或索引的方式。常见类型包括const、eq_ref、ref、range、index和ALL等。
例如:
EXPLAIN
SELECT *
FROM users
WHERE id = 1000;
如果id是主键,优化器通常可以使用非常直接的索引访问方式。
ref常见于通过非唯一索引进行等值匹配,range则常见于范围查询,例如:
WHERE created_at >= '2026-09-01'
AND created_at < '2026-10-01'
ALL表示全表扫描,但“全表扫描”本身并不是错误。
如果一张表只有几百行,优化器可能认为直接扫描整张表比走索引更加便宜。真正需要关注的是:表有多大、实际需要读取多少行、查询多久、执行频率多高。
因此,不要制定“看到ALL就必须加索引”的规则。
二、key告诉你最终用了哪个索引
key是判断索引是否被实际采用的重要字段。
例如表上存在:
CREATE INDEX idx_users_email
ON users(email);
查询:
SELECT id, name
FROM users
WHERE email = 'tom@example.com';
如果执行计划中的key显示idx_users_email,说明优化器选择了这个索引。
但如果key为NULL,也不能直接得出“数据库没有索引”或者“SQL一定很慢”的结论。
可能的原因包括表太小、条件选择性太低、优化器判断全表扫描成本更低,或者现有索引并不适合这个查询。
因此,应该同时查看possible_keys、key、rows以及实际执行时间。
三、联合索引最容易被误解
SaaS系统经常存在类似查询:
SELECT id, status, created_at
FROM orders
WHERE tenant_id = 100
AND status = 1
ORDER BY created_at DESC
LIMIT 50;
如果大量查询都具有这种访问模式,可以考虑针对实际SQL建立联合索引,例如:
CREATE INDEX idx_orders_tenant_status_created
ON orders(tenant_id, status, created_at);
但联合索引并不是“字段越多越好”。
字段顺序决定了索引能够有效利用哪些前导条件。还要考虑等值条件、范围条件、排序要求以及字段选择性。
例如:
INDEX(tenant_id, status, created_at)
与:
INDEX(created_at, tenant_id, status)
并不能简单互换。
因此,索引设计应该从真实SQL出发,而不是先设计一个“万能索引”。
四、rows比很多人想象得更重要
rows表示优化器估算需要检查的行数。
例如两个执行计划都显示使用索引,一个估算需要读取几十行,另一个需要读取数十万行,它们的性能风险显然不同。
但rows是估算值,不是真实执行过程中精确读取的行数。因此不能简单规定“rows超过1000就必须加索引”。
更值得关注的是估算是否严重偏离实际情况。
这也是EXPLAIN ANALYZE非常有价值的原因:
EXPLAIN ANALYZE
SELECT ...
它会实际执行SQL,并提供实际运行过程中的信息。对于慢SQL排查,可以将优化器估算与实际执行情况结合起来观察。
如果估算和实际差距很大,就应该进一步检查统计信息、数据分布以及查询条件,而不是马上继续堆索引。
五、Extra中的Using filesort并不等于磁盘爆炸
Extra包含很多有用的信息。
Using filesort表示MySQL需要采用额外的排序过程。这个名字很容易造成误解,它并不意味着一定在使用磁盘文件,也不代表看到它就必须建立索引。
如果查询只返回很少的数据,排序成本可能完全可以接受。
例如:
SELECT id, name
FROM users
WHERE status = 1
ORDER BY created_at DESC
LIMIT 20;
如果排序涉及的数据规模并不大,那么Using filesort未必是需要解决的问题。
真正需要优化的是高频、大数据量、排序成本明显影响延迟的查询。
Using temporary表示查询过程中使用了临时表等中间处理机制。复杂的GROUP BY、DISTINCT、ORDER BY等操作都可能出现相关执行行为。它是否构成问题,同样需要结合数据规模和实际耗时判断。
Using index通常意味着查询可以直接从索引中获得所需列,也就是常说的覆盖索引场景。这通常有利于减少额外的数据访问,但也不能简单称为“最佳状态”,因为一个复杂查询即使使用覆盖索引,也可能仍然需要处理大量数据。
Using where则只是说明WHERE条件参与了过滤,并不意味着索引有问题。
六、SaaS中的tenant_id要结合数据模型设计
多租户系统经常使用tenant_id进行数据隔离,因此类似:
WHERE tenant_id = ?
AND status = ?
的查询非常常见。
但“所有查询必须包含tenant_id”并不是数据库层面的通用规则。
如果一张表本身就是租户数据表,那么应用和数据访问层确实需要确保租户边界不能被绕过;但系统配置表、国家代码表、汇率表、公共字典表等可能根本不存在租户归属。
更重要的是,不能仅靠开发人员“记得加tenant_id”来保证隔离。Repository、ORM、后台任务、缓存键、消息消费者以及管理接口都需要统一考虑租户边界。
索引方面,如果业务查询长期采用:
WHERE tenant_id = ?
AND created_at >= ?
ORDER BY created_at DESC
那么围绕这种实际访问模式设计联合索引,通常比给tenant_id、created_at分别建立孤立索引更值得研究。
七、不要用SELECT *掩盖真正的I/O成本
查询:
SELECT *
FROM users
WHERE id = ?;
在单行主键查询中通常没有太大问题。
但如果API查询大量记录,SELECT *会让数据库读取和网络传输不必要的字段,也可能让原本可以使用覆盖索引的查询失去这一机会。
例如列表页面只需要:
SELECT id, name, email, created_at
FROM users
WHERE tenant_id = ?
ORDER BY created_at DESC
LIMIT 50;
就没有必要把大型TEXT、JSON或者其他不需要展示的字段全部取出来。
不过也不要为了追求“绝对覆盖索引”而把所有字段都塞进索引。索引本身也会占用空间,并增加写入维护成本。
八、大OFFSET分页是另一个经常被忽视的问题
很多后台系统采用:
SELECT ...
FROM orders
ORDER BY id DESC
LIMIT 100000, 50;
当OFFSET越来越大时,数据库可能需要处理大量前面的记录才能返回最终的50条数据。
对于不断向后翻页的大型数据集,可以考虑Keyset Pagination,也就是基于上一页最后一条记录继续查询,例如:
SELECT ...
FROM orders
WHERE id < ?
ORDER BY id DESC
LIMIT 50;
这种方式尤其适合按照递增或递减主键、时间等稳定字段连续翻页的场景。
九、不要为了EXPLAIN漂亮而优化数据库
实际数据库优化最容易出现的错误,就是为了让执行计划看起来“高级”而不断添加索引。
例如某个查询显示:
type: ALL
key: NULL
开发人员马上建立三个索引;结果SELECT快了一点,却导致INSERT和UPDATE明显变慢,数据库磁盘空间增加,优化器还可能出现更多选择。
正确流程应该是:
先从慢查询日志、APM或者数据库监控找到真正有影响的SQL,再执行EXPLAIN;然后查看访问类型、实际使用索引、估算行数、排序和临时操作;如果需要,再使用EXPLAIN ANALYZE验证真实执行情况。
最后根据实际数据设计索引,并通过压测和生产指标确认优化是否真的有效。
十、EXPLAIN还要结合锁和事务一起看
一条SQL慢,不一定是SQL执行计划慢。
如果查询本身只需要几十毫秒,却因为锁等待最终让API等待几秒,那么单纯研究索引并不能解决问题。
SaaS系统还需要观察事务持续时间、锁等待、死锁、连接池、CPU、磁盘I/O以及并发量。
例如一个看起来使用正确索引的UPDATE,如果长期被另一个长事务阻塞,用户感受到的仍然是“数据库很慢”。
所以EXPLAIN解决的是执行计划问题,而不是整个数据库性能问题。
十一、真正应该建立的是持续诊断体系
对于生产SaaS系统,可以重点观察P95、P99查询延迟、慢查询数量、扫描行数、数据库CPU、磁盘I/O、锁等待、死锁以及连接池使用情况。
发现异常以后,再回到具体SQL。
如果一个查询突然从几毫秒变成几百毫秒,应该检查数据量是否增长、统计信息是否变化、执行计划是否改变、索引是否发生变化,以及是否出现新的锁竞争。
这比定期“给表加几个索引”可靠得多。
EXPLAIN最重要的价值,不是告诉开发人员哪个字段应该建立索引,而是把MySQL优化器准备怎样执行一条SQL展示出来。真正的数据库优化,是理解这个执行计划为什么出现,再判断它与实际业务数据和系统负载是否匹配。
对于SaaS开发者来说,可以把EXPLAIN理解成数据库故障排查中的“诊断仪器”:它提供证据,但最终的判断必须结合SQL、数据规模、访问频率、索引结构、事务和硬件资源共同完成。掌握这一点以后,看到一个ALL或者Using filesort时,就不会条件反射式地拿起“加索引”这把锤子了。
MySQL EXPLAIN 怎么看?SaaS 开发者优化查询必须掌握的几个字段
图片说明:示意图 图片来源:Public Domain(公有领域)
喜欢这篇报道?
使用下面的功能,方便以后继续阅读和分享 MNewsTV
关于文章收藏
收藏不需要注册帐号,收藏信息仅保存在当前浏览器中。
删除收藏请进入「我的收藏」进行管理。
捐助(Paypal): https://www.paypal.me/observeccp 订阅中国观察电报 Telegram : https://t.me/s/ObserveCCP