索引碎片会拖慢查询和写入,表现为页分裂、空洞及物理存储不连续,增加i/o、降低缓冲池命中率,影响范围扫描和order by;data_free>0或size远大于leaf_pages即为碎片信号。

索引碎片确实会拖慢查询和写入
MySQL 的 B+ 树索引在频繁 UPDATE、DELETE、INSERT 后会产生页分裂和空洞,导致物理存储不连续。这会增加磁盘 I/O(尤其是机械盘)、降低缓冲池命中率,并让范围扫描(WHERE id BETWEEN ...)和 ORDER BY 更耗时。InnoDB 的 DATA_FREE 字段大于 0 或 information_schema.INNODB_SYS_INDEXES 中 SIZE 明显大于 LEAF_PAGES,都是碎片明显的信号。
如何判断某张表的索引是否严重碎片化
直接查 INFORMATION_SCHEMA.TABLES 是最快方式:
SELECT TABLE_NAME, ROUND((DATA_LENGTH + INDEX_LENGTH) / 1024 / 1024, 2) AS total_mb, ROUND(DATA_FREE / 1024 / 1024, 2) AS free_mb, ROUND(100 * DATA_FREE / (DATA_LENGTH + INDEX_LENGTH), 2) AS frag_pct FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = 'your_db' AND TABLE_NAME = 'your_table';
一般认为 frag_pct > 25% 就值得处理;若 free_mb > 100 且表本身不大,说明碎片已影响显著。
- 注意:该值对小表(DATA_FREE 最小单位是 1 个 extent(1MB)
-
SHOW INDEX FROM your_table看不到碎片,它只反映逻辑结构 - Percona Toolkit 的
pt-online-schema-change会自动检测碎片,但开销大,慎用于生产高峰
重建索引的三种实操方式及取舍
不是所有场景都适合 OPTIMIZE TABLE,得看版本、锁、空间和业务容忍度:
-
OPTIMIZE TABLE t:MySQL 5.7+ 对 InnoDB 实际执行的是ALTER TABLE t FORCE,会重建聚簇索引和二级索引,释放空页。但会加SX锁(允许读,阻塞写),线上大表可能卡住写请求 -
ALTER TABLE t ENGINE=InnoDB:效果同上,语义更明确,但同样全程锁写 -
ALTER TABLE t ALGORITHM=INPLACE, LOCK=NONE(8.0+):仅当无全文索引、无虚拟列等限制时可用,真正在线重建,但要求磁盘空间翻倍(旧结构未删完前新结构已写入)
小表(OPTIMIZE TABLE 最省事;大表务必提前评估磁盘剩余空间,避免 ALTER 中途失败导致表损坏。
比重建更治本的日常索引优化习惯
碎片是结果,不合理使用才是根源。以下动作能从源头减少碎片生成:
- 避免
CHAR(255)存短字符串——定长字段导致页内浪费,改用VARCHAR并设合理长度 - 批量写入时按主键顺序插入,而不是随机 UUID 或雪花 ID(除非你启用了
innodb_sort_buffer_size和排序预处理) - 删除大量数据后,别只
DELETE,考虑TRUNCATE或分批DELETE+OPTIMIZE组合 - 监控
Innodb_page_splits状态变量,持续升高说明写入模式正在加剧碎片
最常被忽略的一点:没有定期清理的归档表,即使只读,其索引页也可能因 MySQL 内部 purge 线程滞后而残留大量可回收空间——这种“伪碎片”不会被 OPTIMIZE 清掉,得靠 SET GLOBAL innodb_purge_rseg_truncate_frequency = 1 加速清理(需谨慎调优)。











