MySQL优化器认为小表全表扫描更快,是因为顺序读几页聚簇索引的IO成本低于二级索引定位加回表的多次随机IO;当EXPLAIN中rows极小(如≤10)且Extra无Using index condition时,表明优化器理性权衡后主动跳过索引。

MySQL优化器为什么觉得“小表扫全表更快”
因为索引不是免费的——它要额外维护B+树结构、占用内存、引入随机I/O定位开销。当表只有几十或几百行时,优化器算出来:直接顺序读几页聚簇索引(PRIMARY叶子节点),比先查二级索引页、再回表取数据,总成本更低。
这不是误判,是InnoDB对I/O模式的理性权衡:顺序读远快于多次随机跳转。哪怕你建了idx_name,EXPLAIN SELECT * FROM users WHERE name = 'Alice'仍可能显示type=ALL,只要rows≤10且Extra里没Using index condition,就说明它真觉得没必要走索引。
怎么看是不是“真放弃”而不是“没建索引”
别只盯key字段为空,重点看三处:
-
rows值极小(比如2、5、8)→ 优化器预估扫描行数极少,全表扫描代价可控 -
Extra字段为空或仅有Using where→ 没触发索引下推(ICP),也没用到覆盖索引 -
type=ALL但possible_keys里有你的索引名→ 说明索引存在,只是被跳过
反例:rows=987654且表总行数100万 → 那是索引失效或低选择性问题,不是小表策略。
哪些操作会让小表也“被迫走索引”
强制走索引不是好主意,但某些场景下它确实会改变行为:
- 加
FORCE INDEX (idx_col)→ 绕过成本计算,但可能让查询变慢(尤其SELECT *回表多时) - 改
SELECT *为只查索引列,比如SELECT id, name FROM users WHERE name = 'Alice'→ 若idx_name是(name, id),可能触发type=ref+Extra=Using index - 在WHERE中加无意义但可索引的条件,如
AND id > 0(id是主键)→ 有时能“骗”优化器选索引,但不可靠
真正该做的是:确认这是否影响性能。如果SELECT响应稳定在1ms内,type=ALL完全可以忽略。
容易被忽略的底层细节
小表不走索引的背后,藏着两个常被忽视的事实:
- InnoDB的“小表”判断不看行数绝对值,而看预估扫描页数。即使表有5000行,若每页存200行、且
WHERE命中3页以内,仍可能走全表 -
innodb_stats_persistent = OFF时,统计信息靠采样估算,小表的CARDINALITY容易失真,导致优化器误以为索引区分度低而放弃——此时ANALYZE TABLE未必有改善,反而可能让结果更不稳定
所以别急着删索引或加FORCE,先看rows和实际耗时。真正要警惕的,是那些rows不小却还type=ALL的查询。











