3147 字
约 10 分钟
14
S11-A16 · 索引的重建决策:什么时候该重建、什么时候不该
2024-12-31
2026-11-09

"建议每周重建一次索引,能提升性能。"这类建议通常来自一次观察:重建后某个查询确实快了。但那个观察很可能与索引无关——重建过程中表被完整读了一遍,工作集被推进了 buffer pool,重建后第一次查询快,是缓存效应。

重建是一次全量写入:读全表、写全表、占额外空间、抢同一份 IO。代价明确,理由就必须明确。这篇只回答一个问题:什么情况下重建是正解,什么情况下它只是把问题挪到下一个维护窗口。


1. 重建的代价结构:先算账,再决定

代价项 表现 与表大小的关系
IO 放大 全表顺序读 + 全表重写 + 日志写入 近似线性,且远大于一次全表扫
并发受限 COPY 类重建阻塞 DML;INPLACE 类仍需元数据锁与额外空间 持续时间决定影响面
临时空间 需要约等于表大小(或索引大小)的空闲空间 空间不足直接失败
复制延迟 重建在主库产生大量变更,从库追赶变慢 与重建时长同向
统计信息重置 重建后统计信息需要重新采集 短期内执行计划可能更差
备份窗口挤占 全量备份与重建叠加,窗口被撑爆 与备份策略相关
─── 三种重建形态,代价与可用性各不相同(语法按 MySQL 8.0 核对)
$ mysql -e "OPTIMIZE TABLE orders;"                       // 重建以回收空间,期间 DML 表现按版本核对
$ mysql -e "ALTER TABLE orders ENGINE=InnoDB;"            // 显式重建,等同于一次全量拷贝
$ gh-ost --table=orders --alter="ENGINE=InnoDB" ...       // 在线工具:影子表 + 增量回放,慢但不阻塞

判断清单:

  1. 表的数据量与索引量各多大?只重建索引与重建整表的代价不同。
  2. 重建期间能否接受写阻塞?不能接受就只能走在线工具或影子表,时间成本翻倍。
  3. 是否有足够的空闲空间?空间不足时重建失败会留下中间产物。
  4. 从库延迟是否有预算?重建产生的变更量会把延迟推高。
  5. 重建是否落在备份窗口内?两者叠加是典型的自伤。
  6. 重建后谁来验证?没有验证的重建,"变快了"只是感觉。
  7. 这轮重建的预期收益能否写成一句话?写不出来,说明理由还不成立。

一句话判据:重建是"重写一遍",不是"整理一下"。任何以"整理"为理由的重建,都该先被质疑。


2. 重建只能解决三类问题

问题 重建有效吗 说明
碎片化(页填充率低、区不连续) 部分有效 回收空间、提升页利用率;但对点查与范围查的实际影响常被高估
统计信息陈旧 不必要 ANALYZE TABLE 单独更新即可,代价低一个量级
索引结构损坏 有效 页校验失败、索引与数据不一致时,重建是修复手段之一
索引选错、缺索引 无效 这是设计问题,重建只会让错的索引更快
查询写法差、函数包裹列 无效 重建不改变优化器的可用性判断
缓存命中率低 无效 重建带来的"变快"多半是工作集被预热
锁竞争、连接池不足 无效 与索引结构无关

碎片化的真实影响远小于直觉:真正决定代价的是扫描方式:顺序扫描按页连续读,预读有效;随机扫描(大量回表、非聚簇二级索引跳读)每行一次寻址,碎片带来的额外页跳转才被放大。一个以覆盖索引为主、几乎不回表的查询,碎片率再高也感觉不到。

─── 取碎片与页利用率的近似值(information_schema,字段按 MySQL 8.0 核对)
SELECT table_name,
       data_length, index_length, data_free,
       ROUND(data_free / (data_length + index_length + 1) * 100, 2) AS free_pct
FROM information_schema.tables
WHERE table_schema = 'app' AND table_name = 'orders';

─── 统计信息单独更新:不需要重建
ANALYZE TABLE orders;

判断清单:

  1. 慢查询的执行计划是否指向索引结构本身?不是的话,重建无的放矢。
  2. 该表是回表密集还是以覆盖索引为主?前者对碎片更敏感。
  3. 碎片率是统计估算还是精确值?data_free 本身是估算,别拿它当精确指标。
  4. 统计信息是否刚更新过?更新后仍慢,才轮到怀疑结构。
  5. 是否有页校验错误或索引与数据不一致的证据?这是重建的硬理由。
  6. 空间是否真的需要回收?磁盘不紧张时,回收空间本身不构成理由。
  7. 重建后查询计划会不会变?会变时,要准备回退方案。

3. 触发应该基于指标,而不是日历

"每周一次"是最省事的策略,也是最容易变成无收益开销的策略:表在长,重建时间在长,而收益在缩小。最后一轮重建永远跑不完——这不是夸张,是增长曲线与维护窗口的必然交叉。

指标 取法 触发判据(按自家基线校准)
页利用率 / 空闲页占比 data_free 与表大小之比 连续多个观察周期高于基线,且伴随扫描变慢
扫描效率 执行计划中的 rows examined / rows returned 比值持续恶化,且索引未变
查询计划变化率 重建前后同一批 SQL 的计划对比 变化率显著,说明重建确有影响
重建耗时趋势 每轮重建的实际耗时 耗时接近维护窗口的某个比例时,停手
维护窗口余量 窗口长度减实际耗时 余量不足时,重建策略必须改
─── 重建前后抓同一批 SQL 的计划,用于判断"到底变了什么"
EXPLAIN FORMAT=JSON SELECT ... ;   // 重建前存档
─── 重建后重跑,逐项比对 type / key / rows / filtered
// 触发条件写成可执行判断,并带停手条件
rebuild_guard:
  page_free_pct: "高于基线且连续 3 个观察周期"   // 看趋势,不看绝对值
  rebuild_cost: "超过维护窗口 60%"               // 触及即停手,改走分批
  require_baseline: true

判断清单:

  1. 触发条件是否写成了可执行的判断?"感觉慢了"不是条件。
  2. 指标是否在重建之前采集?事后采集无法区分因果。
  3. 是否有基线?没有基线的阈值只是数字。
  4. 是否考虑过缓存效应?重建后的第一次查询快,是因为数据在内存里。
  5. 增长趋势是否被纳入判断?表每年增长一倍时,"每周重建"的可行性会逐年下降。
  6. 停手的条件是否明确?只写何时开始、不写何时停止,是维护任务的通病。

归因错误的经典形态:重建 → 立即压测 → 快了 → 归因于索引。重启实例或预热工作集后同样会快;两条路径分开验证,才知道收益来自结构还是缓存。


4. 副作用、失败中间状态与维护任务冲突

冲突对象 冲突形式 处理方式
全量备份 同一份 IO 与磁盘空间被争抢 错开窗口,或先备份后重建
统计分析 / 报表 大范围扫描与重建叠加 排进同一窗口的另一半,不并行
归档 / 删除任务 大量删除会再次制造碎片 归档完成后再判断是否需要重建,否则白做
主从复制 重建产生的变更把延迟推高 观察延迟,必要时暂停重建
在线 DDL 变更 两个重建同时进行,锁与空间双倍 串行化,禁止并发
─── 失败后先确认中间状态,再决定重试还是回滚(示意,工具与参数按所用版本核对)
$ mysql -e "SHOW CREATE TABLE orders\G" | grep -i engine      // 结构是否被改坏
$ mysql -e "SHOW INDEX FROM orders;"                          // 索引是否齐全
$ ls -lh /var/lib/mysql/app/ | grep -E 'orders.*(tmp|old)'    // 影子表与临时文件残留
$ mysql -e "CHECK TABLE orders;"                               // 页级一致性

判断清单:

  1. 失败时原表是否仍在?COPY 类重建在最后阶段才替换,中途失败通常可回退;在线工具要单独确认。
  2. 是否有临时表与磁盘残留?残留会静默吃掉空间,直到下一次重建失败。
  3. 回滚需要多久?需要重新导入的话,"可回滚"只是理论上的。
  4. 重建是否与变更发布同一窗口?两者叠加时,出问题无法区分原因。
  5. 是否有重建期间的监控?只看结束状态,会漏掉过程中的锁等待与延迟。
度量 说明
碎片率 / 页利用率 判断是否接近触发条件
重建耗时 与维护窗口余量对比
重建后查询计划变化率 判断重建是否影响了执行路径
维护窗口余量 剩余可用时间的比例

5. 排错:五种"重建了但没变好"

现象 原因 处理
重建后立刻变快,一周后回退 缓存效应,工作集被预热 用未重建的同类表做对照
重建后某些查询更慢 统计信息重置,优化器换了计划 重建后立即 ANALYZE TABLE
重建耗时逐轮增长 表在增长,窗口没变 改为按指标触发,并设停手条件
重建期间业务超时 IO 与锁被挤占 换在线工具,或压到更低峰时段
重建失败且空间被占满 临时表与日志残留 先清理残留,再评估是否重试

6. 边界:定期重建几乎没有收益的场景

场景 判断 理由
表很小(能整体放入内存) 不必定期重建 页跳转代价可忽略
写入远多于删除,且以追加为主 不必 碎片主要来自删除与更新留下的空洞
工作集能被 buffer pool 容纳 收益低 缓存掩盖了结构差异
以覆盖索引查询为主 收益低 不回表,随机寻址少
索引结构损坏 必须重建 修复,不是优化
删除/归档后空间需归还磁盘 需要重建 空间回收是明确目标

把重建当修复手段而不是保养手段,判断就简单:有损坏、有明确的页利用率问题、有空间回收需求时做,其余先看执行计划。


自测

  1. 重建的六项代价里,哪两项最容易在评估时被漏掉?
  2. 为什么重建能解决碎片化,却解决不了"索引选错"?
  3. 统计信息陈旧为什么不需要重建,正确做法是什么?
  4. "重建后变快"最可能的另一种解释是什么,如何验证?
  5. 表持续增长时,为什么"每周重建"最终会失效?
  6. 哪些场景下定期重建的收益接近零?
S11-A16 · 索引的重建决策:什么时候该重建、什么时候不该
https://www.clxhxhhr.top/posts/6375/
作者
clxstart
发布于
2024-12-31
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。