update导致avg_fragmentation_in_percent飙升至60%+,根本原因是其触发“标记删除+页分裂”双重动作:更新索引字段等价于delete旧记录+insert新记录,引发逻辑重排与页分裂;重建前须确认高碎索引是否被使用、是否允许锁表,并避免盲目重建主键。

UPDATE 为什么让 avg_fragmentation_in_percent 突然跳到 60%+
根本原因不是 UPDATE 本身,而是它在 B+ 树索引页上触发的“标记删除 + 页分裂”双重动作。InnoDB 和 SQL Server 行为略有差异,但核心机制一致:UPDATE 对含索引字段的修改,等价于一次 DELETE 加一次 INSERT(尤其当索引键值变化时)。
- 如果更新的是索引字段(如
status、updated_at),InnoDB 必须从旧位置移除该记录,并在新键值对应的位置插入——这会直接触发二级索引页的逻辑重排 - 若目标页已满(
avg_page_space_used_in_percent - 更隐蔽的是:UPDATE 后旧记录只被“标记删除”,Purge 线程未及时清理 →
Data_free持续增长,但页内空洞无法被新数据复用(尤其主键非递增时)
哪些 UPDATE 场景会让碎片雪上加霜
不是所有 UPDATE 都一样。以下写法会让碎片生成速度翻倍:
- 批量更新随机主键表的
status字段(如UPDATE orders SET status = 'shipped' WHERE order_id IN (SELECT order_id FROM temp_ids)),导致聚集索引页频繁分裂 - 在触发器中对另一张表做同步
UPDATE(比如审计表计数器),把单次写入放大成多次索引维护 - 复合索引前缀字段被频繁更新(如
INDEX idx_status_created_at (status, created_at)中的status),每次改status都要重排整个 B+ 树分支 - 使用
TEXT/VARCHAR(MAX)列的索引,UPDATE 时 LOB 数据不随页重组移动,造成隐性物理断裂
如何验证是不是 UPDATE 导致的碎片异常
别依赖直觉,用数据定位。重点比对「更新前后」和「有无触发器/高频索引字段」两组条件:
- 执行一次大范围
UPDATE前后,运行:SELECT OBJECT_NAME(object_id), name, avg_fragmentation_in_percent, avg_page_space_used_in_percent FROM sys.dm_db_index_physical_stats(DB_ID(), NULL, NULL, NULL, 'DETAILED'),过滤index_level = 0(叶级页) - 检查
leaf_allocation_count和page_lock_wait_count是否在 UPDATE 后陡增(说明页分裂和锁争用加剧) - 禁用相关触发器或临时删掉疑似冗余索引(如
DROP INDEX idx_status ON orders),重放相同 UPDATE 流量,观察碎片增长率是否下降 40%+ - 监控
innodb_buffer_pool_reads / innodb_buffer_pool_read_requests比值——如果该值周环比跌超 15%,说明碎片已实质性拖慢缓存效率
重建索引前必须确认的三件事
盲目 ALTER INDEX ... REBUILD 可能比碎片本身更危险:
- 确认是哪个索引真高碎:
WHERE avg_fragmentation_in_percent > 30,且该索引user_seeks + user_scans > 0(避免给没人用的索引白忙活) - 确认当前能否锁表:
REBUILD在 Standard 版默认阻塞所有写入;若不能停业务,改用REORGANIZE(但仅对 ≤30% 有效),或 Enterprise 版加WITH (ONLINE = ON) - 主键重建要命:SQL Server 中重建聚集索引会连带重建所有非聚集索引;InnoDB 虽无此问题,但
OPTIMIZE TABLE仍需 2 倍磁盘空间且全程锁表(MySQL 5.7+ 的ALGORITHM=INPLACE不覆盖OPTIMIZE)
真正容易被忽略的点:碎片从不单独存在。它总是和写放大、缓冲池压力、执行计划抖动捆绑出现。只清碎片不调索引结构,等于擦完玻璃又开窗泼水。











