为什么全表扫比走索引更划算
走索引不是免费的,是要付 3 笔账:
1.回表 IO:B+Tree 定位到主键后,要再去聚簇索引取整行数据
2.随机 IO:B+Tree 叶子节点物理位置是分散的,每次回表是 4KB 随机读
3.二级索引读:先读二级索引拿到主键列表,再批量回表
而全表扫是顺序 IO,可以预读(innodb_read_ahead),一次读 16KB、32KB 甚至 1MB 进缓冲池。
临界点估算:
- 假设走索引要回表 N 次 → N 次 4KB 随机读 = N × 4KB
- 全表扫要读 T 字节(表大小)→ T / 1MB 顺序读
- 顺序 IO 速度是随机 IO 的50-100 倍(SSD 上)
- 所以当 N > T / 200KB 时,全表扫就赢了
订单表 1 亿行,热门商品占 80% = 8000 万行 → N = 8000 万 → 8000 万 × 4KB = 320GB 随机读,这账算不过来。
MySQL 优化器怎么算这笔账
MySQL 8.0+ 是CBO(Cost-Based Optimizer),核心是这 3 个数据:
1.table_rows:来自information_schema.tables或 InnoDB 采样估算
2.cardinality(索引基数):唯一值数量,区分度越高越值得用索引
3.clustering_factor:索引顺序和物理顺序的相关度(Oracle 有,MySQL 弱化)
优化器对比两个方案的cost = io_cost + cpu_cost,谁便宜选谁。
关键陷阱:
table_rows和cardinality都是估算值,不准(尤其大表 + 未 ANALYZE TABLE)- innodb_stats_persistent_sample_pages 默认 20 页,统计可能严重失真
- 业务上"热点"是动态的(爆款商品),但统计是离线的(每天/每周更新)——所以"昨天走索引,今天走全表"是真实存在的
怎么判断当前 SQL 走没走索引
EXPLAIN看 4 个字段:
EXPLAIN SELECT * FROM orders WHERE product_id = 12345;
| 字段 | 关注值 | 含义 |
|---|---|---|
type | ALL= 全表扫 /ref/range= 用索引 | type=ALL 就是问题 |
rows | 优化器估算要扫的行数 | rows=10000 但实际是 80000000 = 估算炸了 |
Extra | Using where后还有Using filesort | 走索引但要回表排序 |
filtered | 100 = 全用上 / 10 = 过滤掉 90% | 估算保留比例 |
重点:光看 type=ALL 不一定有问题,要结合rows和表实际大小。
实战 4 步修法(按代价从低到高)
Step 1:先 ANALYZE TABLE(最便宜,0 改动)
ANALYZE TABLE orders; -- 重新采样统计- 适合:统计失真
- 不适合:业务真的"热点数据"(统计反而是准的)
Step 2:让选择性更高(改 SQL / 加索引)
-- ❌ 热点商品 product_id=12345SELECT * FROM orders WHERE product_id = 12345;-- ✅ 加 status 联合条件SELECT * FROM orders WHERE product_id = 12345 AND status = 'PAID';-- 索引变成 (product_id, status),选择性 = 8000w/1亿 × 70% = 56%-- 走索引更划算Step 3:覆盖索引(不查主表)
-- 原 SQLSELECT product_id, user_id FROM orders WHERE product_id = 12345;-- 索引 (product_id, user_id) → 覆盖索引,不回表-- 不用全表扫,也不用回表Step 4:业务层硬拆(最后手段)
- 冷热分离:热数据进 Redis / ES / ClickHouse
- 分库分表:按 user_id 拆,单表行数下来,全表扫也很便宜
- 强制索引:
FORCE INDEX(idx_product_id)——慎用,会让后续优化器失明
"索引不是'有就一定用'。MySQL 优化器是 CBO,它会算两笔账:走索引要回表 N 次,每次 4KB 随机 IO;全表扫要读 T 字节顺序 IO。当热点数据占表 80% 以上时,走索引要回表几千万次,随机 IO 开销反而比全表扫大。判断方法是 EXPLAIN 看 type 是不是 ALL,再看 rows 估算准不准。修法优先级:ANALYZE TABLE → 改 SQL 提选择性 → 覆盖索引 → 业务冷热分离。强制索引 FORCE INDEX 是最后手段,因为硬指定索引会让 CBO 失去对其他场景的适应能力。"