一句话定义
性能反模式是「代码能跑、上线出事」的高频错误集合——N+1 查询、索引失效写法、深分页、大事务、COUNT 滥用等——它们可被静态识别、可被 EXPLAIN 证实、也有成熟的修复方案。
为什么重要
- 90% 的线上慢查询来自重复的少数反模式,掌握清单比掌握玄学调参性价比高一个量级。
- 反模式是 kp-011/kp-012 机制的「症状学」:每个症状都能指回一个机制解释,形成诊断闭环。
- 它是代码评审与容量评审的通用语言:「这里会 N+1」「这是隐式转换失效」一句话即达成共识。
前置知识
kp-011(联合索引与覆盖索引)、kp-012(EXPLAIN 阅读能力)。
核心概念
- N+1 查询:ORM 惰性加载把 1 次关联查询膨胀为 1+N 次往返。
- 索引失效四连:列上套函数/运算、隐式类型转换、前导通配 LIKE、OR 连接非索引列。
- 深分页:
LIMIT 1000000, 20要扫过前百万行,正确姿势是游标(WHERE id < last_id LIMIT 20)或延迟关联。 - 大事务:长事务、事务内 RPC、一次性百万行 DML,拖垮锁、Undo 与复制。
- 回表风暴:
SELECT *让覆盖索引全部失效。 - 计数滥用:实时
COUNT(*)大表做分页总数;应估算、缓存或改「没有下一页」交互。 - 隐式行为依赖:依赖 GROUP BY 方言宽容性(MySQL 非全列分组)导致换库即错。
原理与机制
反模式共通的机制根源有三个。其一是「让索引的白名单失效」:优化器只能在索引可用路径里选路,函数套列、隐式转换、前导 % 都把条件变成无法走树的非 SARGable 谓词,全表扫描随之而来。其二是「往返次数支配延迟」:单次查询 1ms 的 N+1 在网络 RTT 1ms 时总延迟 N+2ms,且连接池被占满——数据库性能问题常是「调用次数问题」。其三是「把开销搬进事务与复制链」:大事务拉长锁持有(kp-016)、撑大 Undo 链(kp-017)、延后一致性点(kp-020),一行代码毁掉三层机制。深分页的机制是 OFFSET 必须物化并丢弃前 N 行,游标分页把「位置」编码为条件谓词,让每页都是一次索引区间扫描。
实例或案例
修复深分页:
-- 反模式:第 50001 页,扫 100 万行再丢弃
SELECT id, title FROM articles ORDER BY id DESC LIMIT 1000000, 20;
-- 修复一:游标分页(索引区间扫描,每页代价恒定)
SELECT id, title FROM articles WHERE id < :last_seen_id ORDER BY id DESC LIMIT 20;
-- 修复二:延迟关联(先用覆盖索引定位 id,再回表 20 行)
SELECT a.id, a.title
FROM articles a
JOIN (SELECT id FROM articles ORDER BY id DESC LIMIT 1000000, 20) t USING (id);
N+1 修复:ORM 层开启预加载(IN 批量取关联)或改写为一次 JOIN;识别手段是在开发环境开 SQL 日志数「同构 SQL 执行次数」。
常见误区
- 用「加缓存」掩盖反模式:N+1 加缓存只是把风暴延后到缓存失效时刻。
- 把慢查询归因于「数据库不行」:EXPLAIN 之前的一切归因都是猜测。
- 批量 DML 一次性提交百万行:应分批(每批数千行)提交,控制锁与复制压力。
自测题
WHERE DATE(created_at) = '2026-10-01'为什么慢?怎么改? 答:函数套列不可走索引(非 SARGable);改为WHERE created_at >= '2026-10-01 00:00:00' AND created_at < '2026-10-02 00:00:00'的区间写法。- 如何在评审时一眼识别 N+1? 答:循环体内出现按外层记录查库的调用(ORM 惰性属性访问);或日志中同构 SQL(仅参数不同)重复出现 N 次。
公式或模型
N+1 总延迟 ≈ 1 次 RTT + N × (RTT + 单查耗时);深分页代价 ≈ OFFSET + page_size 行扫描 vs 游标分页恒定 page_size × log N。
图示
本节不适用:反模式是清单式知识, kp-012 的 EXPLAIN 实验已提供「证据图」,重复画图无增量信息。
直观类比
反模式清单像厨房里的「常翻车菜谱」:油温没到就下锅(索引未就绪就压测)、一锅炖三天(大事务)、客人每点一道菜跑一趟菜市场(N+1)——每条都有名字,说出口大家就知道怎么改。
与其他知识点的关系
kp-011/kp-012 是诊断工具;kp-016/kp-017/kp-020 解释大事务的连锁反应;kp-008 提示「无维护的冗余列」也是一类反模式;kp-030 的容量评估以反模式清理为前提。
延伸阅读
- 《高性能 MySQL》(Baron Schwartz 等)查询性能优化章节
- 《SQL 反模式》(Bill Karwin)全卷