索引数据量过大导致type=ALL或rows异常高,本质是B+树过深、页分裂严重、缓存命中率低;应检查Cardinality、更新统计信息、优化选择性、控制索引长度、合理使用分区及归档策略。

索引数据量过大时,EXPLAIN 显示 type=ALL 或 rows 高得离谱怎么办
这不是索引失效,而是索引本身“太重”——B+树层级过深、页分裂严重、缓存命中率低。MySQL 会倾向跳过它,转而全表扫描。
- 先用
SHOW INDEX FROM table_name看Cardinality是否远低于实际行数,偏低说明统计信息陈旧,执行ANALYZE TABLE table_name更新 - 检查索引字段是否包含高重复值(比如
status TINYINT只有 0/1),这种列单独建索引几乎无意义,应考虑组合索引前置位让选择性更高的字段在前 - 用
SELECT COUNT(DISTINCT col) / COUNT(*) FROM table_name算选择率,低于 0.05 的字段慎作索引首列 - 避免对
TEXT、长VARCHAR字段直接建全文索引以外的索引;真要查前缀,显式用INDEX(col(10))控制长度
分区表能缓解索引体积问题吗?哪些场景真有用
分区表不减少单个索引大小,但能把索引按分区切开,让查询只加载相关分区的索引页,降低内存压力和锁竞争——前提是查询条件能命中分区键(PARTITION PRUNING 生效)。
- 时间范围类查询(如按
create_time分区)最稳妥,WHERE create_time BETWEEN '2024-01-01' AND '2024-06-30'能精准裁剪到 2 个分区 - 用
RANGE或LIST分区,别用HASH——后者无法利用分区裁剪,索引还是全局扫描 - 分区后每个分区仍是独立 B+ 树,所以
ALTER TABLE ... REBUILD PARTITION p1可以在线重建单个分区索引,比全表OPTIMIZE TABLE快得多 - 注意
innodb_file_per_table=ON必须开启,否则所有分区仍挤在 ibdata1 里,起不到物理隔离效果
pt-online-schema-change 重建大索引时卡住或主从延迟飙升
不是工具问题,是索引重建过程本身在持续写 binlog 并触发从库回放压力。尤其当原表有多个二级索引时,每插入一行就要更新全部索引页,放大 I/O 和 CPU 消耗。
- 加
--chunk-index指定一个高选择性、且已存在的索引作为 chunk 切分依据,避免默认用主键导致扫描顺序与写入顺序错位 - 用
--max-load="Threads_running=20"主动限流,比等Slave_SQL_Running_State卡在 “Reading event from the relay log” 更可控 - 如果目标是删掉某个大索引,先
DROP INDEX再跑pt-osc改结构,比带着冗余索引一起重建快 3–5 倍 - 确认从库
relay_log_recovery=ON,否则主库切主后容易因 relay log 不完整导致复制中断
什么时候该放弃索引,改用归档或冷热分离
当单表超 5000 万行、核心索引占用空间 > 20GB、且近 90% 查询集中在最近 3 个月数据时,索引维护成本已远超收益。
- 把历史数据迁出主表:用
INSERT INTO archive_table SELECT * FROM main_table WHERE create_time + <code>DELETE,比TRUNCATE PARTITION更安全(可回滚) - 冷数据表用
ROW_FORMAT=COMPRESSED KEY_BLOCK_SIZE=8,索引页压缩率通常达 40%–60% - 应用层路由查询:写个简单中间件判断时间范围,自动把
WHERE create_time > NOW() - INTERVAL 90 DAY的请求打到主库,老数据走归档库 - 别迷信分区表替代归档——分区只是逻辑切分,删除旧分区仍要锁表(除非
EXCHANGE PARTITION配合空表),而归档是物理移除
真正难的不是选方案,是确认哪部分数据“确实没人查”。上线前用 pt-query-digest --review-history 回溯慢日志,重点看 WHERE 条件里出现频率最低的时间范围段。










