执行计划进阶:读懂filtered和rows的组合,精准判断索引设计质量
大家好,我是小耶,写功课只是为了我踩过的坑,你们别再踩了!
用EXPLAIN看执行计划,大多数人只看三样东西:type是不是ALL、key是不是NULL、Extra有没有Using filesort。但如果只看到这个程度,你离“真正读懂执行计划”还有一段距离。
真正该看的,是rows和filtered的组合。
单独看rows,你只知道“要扫多少行”,但不知道索引筛得干不干净。单独看filtered,你只知道“过滤比例”,但不知道绝对数量有多大。只有把两者放在一起看,才能判断索引设计到底行不行。今天把rows和filtered的组合逻辑彻底拆开讲一遍。
一、rows和filtered分别是什么?
rows:优化器估算的、需要扫描的行数。它是一个相对值,不是精确值,但量级决定了查询成本的基数。
filtered:存储引擎返回的行中,满足剩余WHERE条件的比例。比如rows=1000、filtered=10%,表示存储引擎返回了1000行,经过WHERE条件过滤后最终只留下100行。filtered=100%表示索引精准定位,无需额外过滤;filtered越低,说明索引筛掉的“垃圾”越少,回表后还要过滤掉大量数据。
关键认知:rows是“扫描量”,filtered是“过滤效率”。两者相乘(rows × filtered%)就是你最终需要返回的行数,但更重要的是——filtered越低,意味着索引帮上忙的就越少。
二、三种典型组合及其诊断结论
组合一:rows小、filtered高(✅ 索引设计优秀)
rows=100,filtered=100%。优化器估算扫描100行,全部命中,不需要额外过滤。
诊断结论:索引精准定位了数据,回表代价极小。说明索引列选择性和顺序都很好。
组合二:rows小、filtered低(⚠️ 索引用了,但没完全用)
rows=100,filtered=10%。优化器估算扫描100行,但最终只留下10行,90%的行在回表后被过滤掉。
诊断结论:索引只帮你定位到了一小部分数据,但WHERE条件中还有重要字段不在索引里,导致回表后大量过滤。说明索引设计“缺了东西”——可以把被过滤的字段加进索引,或调整索引列顺序。
组合三:rows大、filtered高(⚠️ 索引命中精准,但扫描范围太大)
rows=100万,filtered=100%。优化器估算扫描100万行,全部命中。
诊断结论:索引确实帮你精准定位了,但问题在于——你要查的数据本身就有一百万行。这说明索引选择正确,但查询条件本身过滤性太差(比如查“2026年全年订单”),或者数据量本身就大。这时候优化方向不是改索引,而是考虑分区表、物化视图或业务层面缩小查询范围。
组合四:rows大、filtered低(❌ 索引设计有问题,最差组合)
rows=100万,filtered=5%。扫描100万行,最终只留5万行。
诊断结论:索引几乎没起什么作用——扫描了大量数据,回表后又过滤掉95%。这是最需要优化的组合。大概率是索引列选择性极差(比如status只有两三个值),或者索引顺序完全不对。优先考虑重建索引或重新设计查询条件。
三、真实案例:从rows和filtered组合定位问题
一个订单表有500万行数据,status字段只有三个值(PAID/UNPAID/REFUND),索引为(status)。业务SQL:
sql
SELECT * FROM orders WHERE status = 'PAID' AND create_time > '2026-01-01';
EXPLAIN输出:
| type | key | rows | filtered | Extra |
|---|---|---|---|---|
| ref | idx_status | 150万 | 30% | Using where |
诊断:rows=150万(status='PAID'占了约30%的数据),filtered=30%(还要再过滤create_time)。组合来看,索引只筛掉了70%的数据,回表后还要再过滤70%,实际有效数据只有45万行。扫描150万行、回表150万次,只为了拿45万行数据——浪费巨大。
优化:改成复合索引(status, create_time)。新EXPLAIN:
| type | key | rows | filtered | Extra |
|---|---|---|---|---|
| ref | idx_status_ctime | 45万 | 100% | Using index condition |
效果:rows从150万降到45万,filtered从30%升到100%,查询时间从4.2秒降到0.3秒。
四、在多表JOIN中,filtered决定了驱动顺序
很多人不知道,filtered在JOIN中还有一个关键作用——它直接影响优化器选择哪个表作为驱动表。
优化器在选择JOIN顺序时,会估算“驱动表行数 × filtered”作为被驱动表的匹配次数。比如:
表A:rows=10000,filtered=50% → 估算匹配5000次
表B:rows=5000,filtered=90% → 估算匹配4500次
优化器可能会选择表B作为驱动表,即使它的rows更小——因为filtered更高意味着更少的无效匹配。这就是为什么有时候你加了个索引,JOIN顺序突然变了——filtered变了,优化器的估算变了。
诊断建议:如果你发现JOIN顺序不合理,检查被驱动表的连接字段是否有索引,以及filtered是否偏低。
五、总结
EXPLAIN的rows和filtered,单独看都没什么用,组合起来才是诊断索引设计质量的关键工具:
| rows | filtered | 诊断 | 方向 |
|---|---|---|---|
| 小 | 高 | ✅ 优秀 | 保持 |
| 小 | 低 | ⚠️ 索引缺字段 | 加入被过滤的列 |
| 大 | 高 | ⚠️ 数据量本身大 | 分区/物化视图/缩小范围 |
| 大 | 低 | ❌ 索引设计问题 | 重建索引或改写SQL |
下次用EXPLAIN的时候,别只看type和key。把rows和filtered两个数字摆在一起,问问自己:这个组合告诉我什么?回答这个问题,你就能从“会看EXPLAIN”升级到“会用EXPLAIN做诊断”。
小耶在手,SQL 不愁
还有什么想了解的,欢迎留言!小耶一定知无不言言无不尽……我们下次见~