【背景】
去年我负责一个 2000 万行的订单流水表(MySQL 8.0,InnoDB),一条报表查询 SELECT ... WHERE tenant_id=? AND status IN (?,?) AND created_at > ? ORDER BY created_at DESC LIMIT 50,P99 从 800ms 涨到 6s,原因很简单:租户维度数据倾斜,某个大租户单月 3000 万行。
当时三个方案摆在我面前:
- DBA 说:加一个
(tenant_id, status, created_at)联合索引,10 分钟搞定; - 开发说:SQL 里
status IN (?,?)改成两个UNION ALL,让优化器走两个更窄的索引,改完不用动表结构; - 架构组说:上 Redis 缓存热租户最近 7 天数据,异步刷新,P99 直接打到 5ms。
三个方案都能用,但代价完全不同——我最后选了「加索引 + 改 SQL」的组合,把缓存留作二期。这个选择事后看是对的,但当时花了 2 小时拉了三个团队对齐才定下来。
【常见分歧 / 几种做法】
我观察到的典型分歧:
- 加索引是「默认解」还是「兜底解」? 很多团队把加索引当第一反应——确实,80% 的慢查询加个索引就解决了。但索引不是免费的:每多一个二级索引,INSERT/UPDATE 多维护一份 B+Tree、多一次聚簇索引回表(或覆盖索引也多吃磁盘 I/O)。一张表 15 个索引之后,写入 P99 涨 30%~50% 并不罕见。
- 改 SQL 是不是「甩锅给开发」?
IN改UNION ALL、子查询改 JOIN、SELECT *改指定列——这些改动开发 5 分钟能做完,但「谁来发现该改」往往是 DBA 的巡检结果。责任边界模糊时,容易变成「DBA 说慢 → 开发说不是我的问题 → 架构组说这是设计问题」。 - 缓存是「银弹」还是「延迟炸弹」? 缓存确实能把 P99 打到个位数 ms,但引入了一致性窗口、缓存穿透/雪崩、多节点缓存失效广播等新问题。对于「读多写少 + 数据变更频率可预测」的场景,缓存收益巨大;对于「高频写入 + 强一致要求」的场景,缓存反而让问题更难排查。
【我的倾向与理由】
我个人的决策顺序(不一定对,欢迎拍砖):
- 第一步永远是
EXPLAIN+EXPLAIN ANALYZE,搞清楚慢在哪:是索引没命中、回表太多、还是 filesort/temp table?这一步 5 分钟,但能排除 60% 的「玄学慢」。 - 如果索引缺失或索引选择性不足 → 加/换索引,这是成本最低的改动(DDL 在线执行,不锁表),优先做。
- 如果索引已经合理但 SQL 写法导致优化器选错计划 → 改 SQL(
STRAIGHT_JOIN、拆UNION ALL、加FORCE INDEX是最后手段)。 - 如果查询模式天然不适合索引(如
LIKE '%xxx%'、跨表聚合、多租户大表扫描) → 才考虑缓存、物化视图、或读写分离到只读副本。 - 一个原则:能用表结构解决的,不用应用层补丁;能用应用层解决的,不引入新的中间件。
【想听大家在什么场景下怎么选】
具体想听几类经验:
- 你们团队慢查询治理有没有 SOP?「谁先动」是 DBA、开发、还是 SRE 值班?
- 你们有没有遇到过「加完索引反而更慢」的情况(比如索引选择性太差导致优化器走错)?当时怎么定位的?
- 大租户 / 数据倾斜场景下,你们的索引策略和普通表有区别吗?
- 缓存和索引的边界你们怎么划?有没有一个「缓存 TTI(time to invalidate)超过 X 秒就放弃缓存」的经验阈值?
- 一个争议题:
FORCE INDEX/IGNORE INDEX在你们的生产环境是「禁区」还是「工具」?用过的踩坑经历欢迎分享。
这个话题不追求一个标准答案,想看看不同规模(10 人小团队 vs 100 人平台组)下,大家实际的治理路径长什么样。
0赞
0收藏
0楼层