EXPLAIN ANALYZE 耗时不准因受缓存、锁和系统负载干扰,应取后两次 Execution Time 平均值,并关注 Buffers(PG)或 Handler_read_*(MySQL)等真实 I/O 指标,而非单次耗时。
查 EXPLAIN ANALYZE 时为什么耗时不准
因为单次执行受缓存、锁、系统负载干扰太大,直接看 execution time 容易误判索引优劣。真正要对比的是「稳定状态下的执行路径和资源消耗」,不是某一次快慢。
- 务必在相同数据量、相同连接会话、关闭查询缓存(
SET SESSION query_cache_type = OFF)下测试 - 首次执行会触发磁盘读和 buffer pool 加载,至少执行 3 次,取后两次的
Execution Time平均值 -
EXPLAIN ANALYZE输出里的Buffers行(PostgreSQL)或Handler_read_*状态(MySQL)比耗时更反映索引实际效率
MySQL 下怎么让 EXPLAIN 显示真实 I/O 开销
默认 EXPLAIN 不显示磁盘读,得靠 SHOW STATUS LIKE 'Handler_%' 配合清零再执行 SQL,才能看到索引实际扫了多少行、回表几次。
- 执行前先运行
FLUSH STATUS,再跑目标 SQL - 立刻查
SHOW STATUS LIKE 'Handler_read%':重点关注Handler_read_next(索引扫描行数)、Handler_read_rnd(回表次数),越小越好 - 如果
Handler_read_rnd远高于Handler_read_next,说明索引没覆盖查询字段,正在大量随机 IO 回表
PostgreSQL 中 EXPLAIN (ANALYZE, BUFFERS) 关键看哪几行
重点不是最上面的总耗时,而是每层节点的 Buffers: shared hit=xxx read=yyy 和 Rows Removed by Filter。
-
shared read=是真磁盘读,>0 就说明索引或表没被缓存住;反复测试时这个值应趋近于 0 才算稳定 - 如果某层出现
Rows Removed by Filter: 9990/10000,说明索引选型错误——它扫了 1 万行,只留下 10 行,过滤成本远高于索引收益 - 注意
Index Scan using idx_a_b on t后面有没有Index Cond: (a = $1)和Filter: (b > $2)并存:后者意味着 b 字段没被索引有效利用
SQL 相同但执行计划突变?检查这三处隐性干扰
看起来是同一句 SQL,但执行计划可能因隐式类型转换、统计信息过期、绑定变量窥探而完全不同,导致索引对比失效。
- 用
SELECT pg_typeof(col)确认 WHERE 条件中字段和参数类型严格一致,避免WHERE id = '123'触发隐式转 cast 导致索引失效 - 执行
ANALYZE table_name(PostgreSQL)或ANALYZE TABLE table_name(MySQL),确保优化器基于最新数据分布做决策 - MySQL 中如果用了预处理语句,第一次执行的执行计划会被复用,加
/*+ RECOMPILE */或改用普通语句重试










