索引碎片确实会拖慢查询和写入
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加速清理(需谨慎调优)。
