小表全表扫描比索引查找更快,是因为B+树索引需至少2次随机IO(索引页+回表聚簇页),而全表扫描仅1次顺序IO且易缓存于buffer pool;当数据页≤3–5页时优化器通常放弃索引。

小表全表扫描比索引查找更快的底层原因
MySQL 在数据量极小(比如几百行以内)的表上建索引,往往反而拖慢查询。根本不是“索引没用”,而是
innodb_buffer_pool_size
和实际页数
是否走索引,MySQL 优化器会估算代价,而估算依据之一是“这个表能被缓存在 buffer pool 里多少”。如果整个表的数据页都能常驻内存,全表扫描就几乎全是内存操作,速度碾压磁盘随机读索引页。
- 用
SELECT COUNT(*) FROM information_schema.INNODB_BUFFER_PAGE WHERE TABLE_NAME = 'your_db/your_table';粗略看当前缓存了多少页(注意权限和性能影响) - 更实用的方法是查表的物理大小:
SELECT DATA_LENGTH, INDEX_LENGTH FROM information_schema.TABLES WHERE TABLE_SCHEMA='your_db' AND TABLE_NAME='your_table';,再除以 16384 得到理论页数 - 经验阈值:若数据页 ≤ 3–5 页(即数据量 ≈ 50KB–80KB),优化器大概率放弃索引,强制走全表扫描
FORCE INDEX 强制走索引在小表上反而变慢的典型场景
有些同学发现加了 WHERE 条件后执行计划没走索引,就手动加 FORCE INDEX,结果 QPS 下降、延迟飙升——这恰恰暴露了对代价模型的误判。
常见触发条件:
- 查询条件选择性极高(如
id = ?),但表本身只有 200 行,优化器认为全表扫更快 - 联合索引中只用了最左前缀的一部分,且字段区分度低(如
INDEX(status, created_at),却只查status = 1),导致索引扫描行数接近全表,还多一次回表 - 开启了
innodb_stats_persistent = OFF,统计信息陈旧,优化器误估索引效率
验证方式:执行 EXPLAIN FORMAT=JSON,重点看 query_cost 字段,对比 used_key_parts 和 rows_examined_per_scan
真正该建索引的小表特征:高频、高过滤、非主键访问
小表不等于“永不建索引”,当满足以下组合条件时,索引依然有价值:
- 该表被频繁 JOIN(尤其是作为被驱动表),且 ON 条件列未命中主键(例如
JOIN orders ON users.id = orders.user_id,orders.user_id就值得建索引) - 有高频
ORDER BY ... LIMIT 1查询,且排序字段不是主键(避免 filesort) - 存在
SELECT ... WHERE col IN (val1, val2)且col区分度高,而全表扫描需遍历所有行做匹配 - 表虽小,但字段宽(比如含多个 TEXT 或长 VARCHAR),导致单页存储行数极少,全表扫描实际要读更多页
记住:索引的价值不在“表大小”,而在“访问模式是否让索引的有序性和定位能力不可替代”。盲目按行数划线建或不建,最容易在后期数据缓慢增长后突然翻车——那时页数刚跨过临界点,执行计划突变,监控却未必立刻报警。











