
为什么全表扫比走索引更划算走索引不是免费的是要付 3 笔账1.回表 IOBTree 定位到主键后要再去聚簇索引取整行数据2.随机 IOBTree 叶子节点物理位置是分散的每次回表是 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 是CBOCost-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 TABLEinnodb_stats_persistent_sample_pages 默认 20 页统计可能严重失真业务上热点是动态的爆款商品但统计是离线的每天/每周更新——所以昨天走索引今天走全表是真实存在的怎么判断当前 SQL 走没走索引EXPLAIN看 4 个字段EXPLAIN SELECT * FROM orders WHERE product_id 12345;字段关注值含义typeALL 全表扫 /ref/range 用索引typeALL 就是问题rows优化器估算要扫的行数rows10000 但实际是 80000000 估算炸了ExtraUsing where后还有Using filesort走索引但要回表排序filtered100 全用上 / 10 过滤掉 90%估算保留比例重点光看 typeALL 不一定有问题要结合rows和表实际大小。实战 4 步修法按代价从低到高Step 1先 ANALYZE TABLE最便宜0 改动ANALYZE TABLE orders; -- 重新采样统计适合统计失真不适合业务真的热点数据统计反而是准的Step 2让选择性更高改 SQL / 加索引-- ❌ 热点商品 product_id12345SELECT * 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 失去对其他场景的适应能力。