大家好,我是小耶,写功课只是为了我踩过的坑,你们别再踩了!
前两天一个同事跑过来问我:"小耶,你看这个执行计划,type是ref,rows是1000,Extra里用了索引,为啥还是慢?"
我看了眼SQL,又看了眼他的执行计划,问了一句:"你用的是EXPLAIN还是EXPLAIN ANALYZE?"
他说:"EXPLAIN啊,用了十几年了。"
------问题就出在这儿。
EXPLAIN给你看的是"优化器的预测",不是"实际执行的结果" 。优化器说rows=1000,实际可能扫了100万行;优化器说用索引,实际可能回表回了10万次。
MySQL 8.0.18开始引入的EXPLAIN ANALYZE,直接把实际执行时间和实际行数输出给你看,把执行计划分析这件事从"猜"变成了"看"。
一、传统EXPLAIN的局限
传统的EXPLAIN输出,你看到的是:
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
|---|---|---|---|---|---|---|---|---|---|
| 1 | SIMPLE | orders | ref | idx_user_id | idx_user_id | 4 | const | 1000 | Using index condition |
rows=1000 ------ 这是优化器估算的需要扫描的行数,不是实际扫描的行数。
如果统计信息过期、数据分布倾斜、或者优化器的基数估算出了偏差,这个数字可能和实际情况差一个数量级。
你基于一个错误的估算去优化,方向可能从一开始就偏了。
二、EXPLAIN ANALYZE带来了什么
EXPLAIN ANALYZE实际上会执行SQL (是的,它会真的跑一遍),然后输出每个执行步骤的实际执行统计信息:
bash
EXPLAIN ANALYZE
SELECT * FROM orders WHERE user_id = 12345 AND create_time > '2026-01-01';
输出示例(MySQL 8.0+):
bash
-> Filter: (orders.create_time > '2026-01-01') (cost=1234 rows=1000) (actual time=0.5..45.2 rows=856 loops=1)
-> Index lookup on orders using idx_user_id (user_id=12345) (cost=1234 rows=1000) (actual time=0.4..42.1 rows=12345 loops=1)
关键差异:
| 传统EXPLAIN | EXPLAIN ANALYZE |
|---|---|
| 估算行数(rows) | 实际行数(actual rows) |
| 估算成本(cost) | 实际执行时间(actual time) |
| 只看计划 | 看到每个步骤的真实开销 |
在上面这个例子里,优化器估算rows=1000,实际扫描了rows=12345------差了12倍。如果你只看传统EXPLAIN,可能觉得"只扫1000行,没问题",但实际上回表回了1万多行,慢是必然的。
三、怎么用EXPLAIN ANALYZE定位问题
第一步:找到最慢的那个步骤
actual time告诉你每个步骤实际花了多少毫秒。哪一步时间最长,哪一步就是瓶颈。
第二步:对比估算值和实际值
如果rows和actual rows差距很大(比如差10倍以上),说明统计信息可能过期了------先更新统计信息,再看看执行计划有没有变化。
第三步:看loops
loops表示这个步骤被执行了多少次。如果loops很大(比如>1000),说明有大量的重复执行------可能是嵌套循环连接(Nested Loop Join)的驱动表选错了,或者子查询被反复执行。
四、注意事项
-
EXPLAIN ANALYZE会实际执行SQL ,所以不要在写操作上直接跑------先在测试环境或只读从库上验证 -
对于耗时很长的SQL,
EXPLAIN ANALYZE本身也要跑那么久,要有耐心 -
MySQL 5.7及以下版本不支持,需要升级到8.0.18+
-
EXPLAIN ANALYZE的输出比传统EXPLAIN详细得多,需要花时间熟悉格式
五、小结
EXPLAIN用了十几年,是时候升级到EXPLAIN ANALYZE了。前者给你看"优化器的预测",后者给你看"实际执行的结果"。从"猜"到"看",这是执行计划分析这件事上最大的认知升级。
小耶在手,SQL 不愁
还有什么想了解的,欢迎留言!小耶一定知无不言言无不尽......我们下次见~