【背景】 去年接手一张 5200 万行的订单主表(MySQL 8.0.36,InnoDB,16C64G 单机),接手时已经挂了 11 个二级索引。业务方最痛的一条慢查询:按「商户号 + 下单时间范围 + 状态」做分页查询,没走索引时 P99 飙到 1.8s,加了复合索引后 P99 回到 12ms,很爽。
爽了三个月。
QPS 从 1200 涨到 3500 之后,问题来了:P50 从 8ms 慢慢爬到 22ms,P99 也开始间歇性抖到 80ms+。排查下来,根因不是单条查询慢,而是 11 个二级索引 + 1 个聚簇索引 = 12 棵 B+Tree 的写放大——每插入一行,InnoDB 要维护 12 棵树的节点分裂与 redo log 写入。高并发下 lock wait 和 purge 延迟被放大,P50 被"拖底"了。
砍掉 4 个低命中的二级索引之后,写 P50 回到 9ms,但有两个报表查询从 12ms 回到 40ms。这就是典型的"加索引治 P99、写放大伤 P50"的拉锯。
【常见分歧——我见过三种立场】
立场 A:「索引是免费的,慢查询才是要命的」 逻辑:读 QPS 是写 QPS 的 5~20 倍,一个索引能救 1000 QPS 的慢查询,写放大只多 1 棵树,完全值得。 我见过不少团队一张表挂 15+ 个索引,DBA 不敢动,因为"不知道哪个索引哪个报表在用"。
立场 B:「写放大是隐性的,等你发现时已经晚了」 逻辑:二级索引的写代价不只是多一棵树,还涉及:
- 每次写要额外维护二级索引的 undo log(回滚时二级索引的 undo 量是聚簇索引的好几倍)
- 页分裂时,二级索引的页分裂比聚簇索引更频繁(二级索引的 key 是 (列值, PK),列值分布不均时分裂率更高)
- purge 线程要同时清理聚簇索引和所有二级索引的 delete mark,索引越多 purge 积压越严重 这个立场的极端做法是"一张表最多 3~4 个二级索引,超了就改 SQL 或加缓存"。
立场 C:「别加索引了,改 SQL / 拆查询 / 加物化视图」 逻辑:很多"需要加索引"的场景,本质是 SQL 写法的问题——比如 WHERE 里套函数、隐式类型转换、不必要的 JOIN。真正的索引缺口应该通过改写来解决,而不是无脑加列。 这个立场我部分认同,但实际推不动——业务方不愿意改 SQL,DBA 加一个索引只要 5 分钟 DDL(MySQL 8.0 的 online DDL),改 SQL 要排 2 周需求。
【我的倾向与理由】
我站在 B 和 C 的交叉地带,具体做法:
-
给每张核心表设一个「索引预算」:我的经验值是 单表二级索引 ≤ 5 个(不含主键/唯一键)。这不是教条,而是基于一个粗略的写放大模型:
- InnoDB 每次 INSERT 的 redo log 量 ≈ 1(聚簇)+ N(二级索引)份的页变更
- 当 N > 5 时,高并发(>2000 TPS)下 redo log flush 和 checkpoint 的 tail latency 开始显著影响 P50
- 这个数字会随硬件(SSD vs HDD)、并发度、行大小变化,但 5 是一个"安全区间上限"
-
加索引前必须算「写放大税」:
- 当前写 TPS × 新增索引数 = 额外的 B+Tree 维护次数
- 用
SHOW ENGINE INNODB STATUS里的History list length和Log.flushed速率做基线 - 加完索引后监控 48h 的 P50/P99 写延迟,如果 P50 涨幅 > 30%,必须砍
-
砍索引的优先级:按「命中率 × 查询频率」排序:
- 用
performance_schema.table_io_waits_summary_by_index_usage拉 7 天数据 - COUNT_STAR / (COUNT_STAR + COUNT_READ) < 10% 且无业务方认领的,排最前
- 砍之前先和业务方确认"你确定没有报表在用这个索引"——90% 的情况是"不确定,但应该没有"
- 用
-
真正该加索引但"预算已满"的场景,优先走改写:
- 能拆成两步查询的(先查 ID 再查详情),不要用一个宽复合索引
- 能用覆盖索引避免回表的,比加一个"宽索引"对写放大更友好(覆盖索引的叶子节点只存 PK,更紧凑)
【想听大家在什么场景下怎么选】
- 你们线上核心表(千万级+)的二级索引上限是几个?有没有踩过"索引太多导致写 P50 恶化"的坑?
- 立场 A 的团队,你们是怎么管理 15+ 索引的?有没有自动化的"索引健康度"巡检?
- 你们砍索引的流程是什么?DBA 直接砍 vs. 业务方确认 vs. 灰度下线?有没有"砍了之后报表炸了"的血泪史?
- MySQL 8.0 的
information_schema.stats(直方图)和performance_schema的索引使用率数据,你们信任度多高?有没有遇到"数据说没用、实际有用"的误判?
我抛砖引玉,期待不同立场的朋友来拍砖。