innodb无直接碎片率指标,可通过data_free与数据量比值及页使用率评估;optimize table实际重建表且风险高;逻辑碎片需结合explain和show index分析;生产环境推荐pt-online-schema-change等在线方案。

怎么查 InnoDB 表的索引碎片率
InnoDB 本身不直接暴露“碎片率”这个数值,但可以通过 information_schema.INNODB_SYS_INDEXES 和 information_schema.INNODB_SYS_TABLES 关联查出索引的页使用率(FILL_FACTOR 的近似替代),更实用的是看 DATA_FREE 和实际数据量的比值。
常用实操命令:
SELECT t.NAME AS table_name, i.NAME AS index_name, i.N_FIELDS AS cols, i.PAGE_NO, t.ROW_FORMAT, t.DATA_FREE / 1024 / 1024 AS data_free_mb, t.FILE_FORMAT, ROUND((t.DATA_LENGTH + t.INDEX_LENGTH) / 1024 / 1024, 2) AS total_size_mb FROM information_schema.INNODB_SYS_TABLES t JOIN information_schema.INNODB_SYS_INDEXES i ON t.TABLE_ID = i.TABLE_ID WHERE t.NAME LIKE 'your_db/your_table' ORDER BY t.DATA_FREE DESC;
-
DATA_FREE是已分配但未使用的空间(单位字节),持续增删改后容易变大,>100MB 就该关注 -
ROW_FORMAT=DYNAMIC或COMPACT下,DATA_FREE高通常意味着页分裂严重 - 注意
t.NAME格式是database/table,不是 SQL 中的反引号包裹名
为什么 OPTIMIZE TABLE 有时没用、有时卡住
OPTIMIZE TABLE 对 InnoDB 实际执行的是 ALTER TABLE ... FORCE(重建表),它会触发全表拷贝+重建索引,代价高,且在 MySQL 5.6+ 默认开启 innodb_file_per_table=ON 时才真正释放磁盘空间。
- 如果
innodb_file_per_table=OFF,OPTIMIZE TABLE不会缩小ibdata1,只整理内部页,DATA_FREE可能不变 - 执行时会加
SUPER权限锁,且阻塞 DML;线上大表慎用,尤其没配innodb_online_alter_log_max_size时可能 OOM - MySQL 8.0+ 支持
ALGORITHM=INPLACE的部分优化,但仅限某些 DDL 场景,OPTIMIZE TABLE仍默认走 COPY
用 SHOW INDEX 和 EXPLAIN 辅助判断逻辑碎片
物理碎片(DATA_FREE)之外,更要警惕逻辑碎片:比如索引 B+ 树深度异常、叶子节点空洞多、范围查询响应慢——这些不会体现在 DATA_FREE 里,但直接影响查询性能。
-
SHOW INDEX FROM your_table看Cardinality是否明显低于实际行数,偏低可能说明统计信息陈旧或索引选择性差 -
EXPLAIN SELECT * FROM your_table WHERE indexed_col = ?中key_len偏小、rows远大于实际命中数,常是索引页内键值分布稀疏的表现 - 对写密集表,定期跑
ANALYZE TABLE更新统计信息,否则优化器可能误判索引有效性
线上环境安全清理碎片的替代方案
直接 OPTIMIZE TABLE 风险高,生产库更推荐渐进式、低侵入方式。
- 用
pt-online-schema-change(Percona Toolkit)做在线重建:pt-osc --alter "ENGINE=InnoDB" --execute D=your_db,t=your_table,支持暂停、限速、校验 - MySQL 8.0.29+ 可尝试
ALTER TABLE ... REBUILD,比OPTIMIZE更轻量,不重建表结构只整理页 - 对超大表,优先考虑按时间分区(
PARTITION BY RANGE),再用TRUNCATE PARTITION或REORGANIZE PARTITION局部整理
碎片问题从来不是“查完就清”,而是和写入模式、主键设计、事务长度强相关。比如用 UUID 做主键,几乎必然导致高频页分裂——这时候调优索引比反复 OPTIMIZE 有用得多。











