一、为什么“碎片化”在高频更新里被放大
在高并发写入、更新、删除的业务中,B+ 树(或倒排索引、LSM 树)为了维护有序性和页填充率,会不断拆分、合并、移动页。每一次 UPDATE 都可能把原页拆成两半,留下半空页;每一次 DELETE 又会在页内产生空洞。久而久之,“逻辑相邻、物理分散”的碎片便大量出现。碎片带来的直接后果是:
1. 查询需要扫描更多页,I/O 放大;
2. 缓存命中率下降,内存浪费;
3. 写入放大,SSD 寿命缩短;
4. 优化器误判,选择次优执行计划。
二、量化碎片化:把“感觉”变成可观测指标
1. 行存储索引
- 逻辑碎片(logical fragmentation):页在物理上不连续的比例,>30 % 通常视为严重。
- 扫描密度(scan density):理想扩展区与实际扩展区之比,<70 % 需关注。
- 页密度(page density/avg_space_used):页内有效数据占比,<80 % 说明空洞多。
2. 列存储索引
- 删除行占比(deleted_rows/total_rows),>20 % 建议压缩重组。
3. 倒排索引(全文检索)
- Segment 数量与刷新频率:segment 过多会导致查询时合并开销增大。
监控采集手段:
- 系统视图:行存储用 `dm_db_index_physical_stats`,列存储用 `dm_column_store_row_group_physical_stats`,倒排用 `_stats` API。
- 采样策略:
– 高频表:每 30 分钟采样;
– 低频表:每 6 小时采样;
– 采样时避开业务尖峰,防止额外 I/O。
三、在线重建的四种武器
1. REORGANIZE(整理)
- 只重新排列叶级页,不新建索引;
- 全程持意向锁,读写不阻塞;
- 效果温和,适合“日常保养”。
2. REBUILD(重建)
- 创建新索引树后原子切换;
- 默认需排他锁,可开启 ONLINE 选项:
– 行级锁 + 行版本快照,读写并发;
– 重建时间拉长 2–3 倍,但业务无感知。
3. 分区级 REBUILD
- 仅对命中分区操作,降低锁粒度;
- 适合时间分区表(如日志表)。
4. 零停机工具链
- 利用影子索引 + 增量同步 + 原子切换;
- 或借助第三方工具(基于触发器或 binlog)实现无锁迁移。
四、自动化工作流:从监控到自愈
1. 采集器:定时任务拉取碎片指标,写入时序库;
2. 决策器:根据阈值 + 业务窗口(凌晨低峰)生成维护任务;
3. 调度器:
– 在线轻度 → 立即 REORGANIZE;
– 重度 → 排队至维护窗口在线 REBUILD;
– 紧急 → 人工确认 + 影子索引。
4. 反馈器:任务完成后 10 分钟自动对比执行计划与延迟曲线,验证效果;若未达标,回滚并告警。
五、高频场景下的特殊策略
1. 填充因子(fillfactor)预调
- 更新频繁的表设为 70–80 %,预留空洞降低页拆分频率;
- 查询为主的维度表保持 90–100 %。
2. 分区 + 局部索引
- 按时间或 Hash 分区,把高频更新集中在最新分区;
- 局部索引仅重建热点分区,减少整体 I/O。
3. 批量合并写
- 将单行 UPDATE 合并为批量 MERGE,降低页抖动次数。
4. 定期统计信息刷新
- 重建索引后,立即更新统计信息,避免优化器误判。
六、案例:订单表 30 天治理闭环
- 表规模:2 TB,日更新 500 万行,P99 查询延迟 120 ms。
- 初始碎片:逻辑碎片 55 %,页密度 60 %。
- 治理步骤:
1. 分区级 REBUILD(在线),仅处理最近 7 天分区;
2. 填充因子从 100 % 调至 75 %;
3. 开启增量统计信息更新。
- 结果:逻辑碎片降至 8 %,页密度 85 %,P99 延迟降至 35 ms;存储空间节省 18 %。
七、常见陷阱与回滚
1. 盲目全库重建:I/O 洪峰导致主从延迟;
2. 忽视统计信息:重建后反而更慢;
3. 高峰期在线重建:锁升级阻塞业务。
回滚策略:
– 维护任务超时自动中断;
– 影子索引一键切换回原索引;
– 实时监控写入队列长度,超过阈值即终止任务。
八、结语
索引碎片化不是“脏了再擦”,而是“边跑边保养”。通过可观测的指标体系、分级的阈值策略、在线的重建工具,以及闭环的自动化流程,开发团队可以把碎片治理融入日常运维,让高频更新不再成为性能衰退的元凶,而是可控、可度量、可自愈的系统特性。