【背景】
做了 15 年 DBA,慢查询治理是日常中的日常。但每次拉上开发一起看 slow log,第一句话永远是"加个索引吧"。这句话听起来没毛病——5 分钟搞定、开发不用改代码、风险最低。
但问题是:这个"第一反应"在很多场景下是错的,或者至少不是最优解。
【我见过的几种典型分歧】
派 A(加索引优先): "索引最便宜,EXPLAIN 一看 type=ALL 就加,开发不用动,上线即生效,为什么要先改 SQL?改 SQL 要回归测试,周期长,风险高。"
派 B(改 SQL 优先): "索引是负债。我见过一张表 14 个二级索引,每次 INSERT 要维护 14 棵 B+Tree,写入 P99 直接翻倍。先看看那条 SQL 是不是把 3 张表 JOIN 了 5 个字段、WHERE 条件写了个 LIKE '%xxx%',改一下 SQL 可能根本不需要新索引。"
派 C(看影响面): "别一上来就治理。这条慢查询 QPS 才 2、平均 800ms,一天也就几百次调用,用户感知不到。你花 2 天优化它,不如把 P99 从 15ms 降到 12ms 的那个 QPS 5000 的接口搞定。先算 QPS × 延迟增量 × 业务权重,再决定值不值得动。"
派 D(写入税视角): "你们有没有算过'一个索引的写入税'?我们一张核心订单表,加一个索引后 INSERT 的 P99 从 3ms 涨到 4.2ms。单看 1.2ms 不多,但 QPS 8000 的写入量下,整个系统的 P99 都被抬了。索引不是免费的,每个都是持续的写入成本。"
这四种说法其实都对,但适用场景不同,团队里经常吵不出结论。
【我的倾向与理由】
我个人的排序原则是成本递增、收益递减:
- 先量化影响面:QPS × (P99 - P50) × 业务权重,算出这条慢查询对整体 SLO 的实际拖累。拖累 < 0.1% 的,进 backlog 不急着治。
- 改 SQL(0 额外成本):减少回表(用 covering 查询)、拆 JOIN 为子查询、去掉不必要的 SELECT *、把 LIKE '%xxx%' 换成全文索引或搜索引擎。这一步不需要加索引,不需要 DDL,回归测试即可。
- 加覆盖索引(低写入税):比加普通索引多一个"覆盖"约束——索引列必须包含 SELECT 和 WHERE 涉及的列,避免回表。写入税和普通索引一样,但读收益更高。
- 加普通索引(中写入税):EXPLAIN 确认 type 从 ALL 升到 ref/range,但写入要维护额外 B+Tree。加之前跑
sys.schema_index_statistics看该表的写入 QPS,写入 > 500 QPS 的表要特别谨慎。 - 拆表 / 分库(高成本):前四步都走不通再考虑。
关键纪律:每次加索引,同步算写入税。方法很简单——在 staging 上跑压测,对比加索引前后 INSERT/UPDATE 的 P99 和吞吐,记录到索引台账里。没有这个数据,"加个索引吧"就永远是最便宜的话。
【想听大家怎么说】
- 你们团队里"加索引"和"改 SQL"的优先级是怎么定的?有没有书面规范,还是靠 DBA 拍脑袋?
- 有没有人量化过"一个二级索引的写入税"?具体数字是多少(P99 涨多少 ms、吞吐降多少 %)?
- 派 C 说的"影响面不够就不治",你们能落地吗?PM 会不会说"用户投诉了就是 100%"?
- 你们有没有"索引台账"——记录每个索引是谁加的、为什么加、写入税多少、是否还在用?
欢迎拍砖,尤其是"加索引派"的朋友,来聊聊什么场景下加索引真的是最优解。