防慢查询的关键是从代码编写阶段规避陷阱:避免循环中执行sql、慎用select*、合理控制分页深度、统一SQL构建逻辑,并落实防御意识。

SQL慢查询的根源往往不在数据库本身,而在应用层的写法和设计逻辑。防慢查询的关键,不是等它发生再加索引或调优,而是从代码编写阶段就规避常见陷阱。
避免在循环中执行SQL
这是最典型也最容易被忽视的性能杀手。比如遍历用户列表,对每个用户查一次订单——100个用户就发100次查询,网络开销、连接建立、解析执行全被放大。
- 改用批量查询:用 IN 或 JOIN 一次性拉取所需数据,注意 IN 参数数量不宜过多(一般不超过 500,超量建议分批)
- 使用预加载(如 mybatis 的 Collection / association,或 ORM 中的 select_related、prefetch_related)提前关联数据
- 必要时用内存组装:先查主表ID,再用这些ID批量查子表,最后在代码里做映射
慎用 SELECT *
读取全部字段不仅增加网络传输和内存消耗,还可能让优化器无法走覆盖索引,甚至因大字段(TEXT、BLOB)拖慢整行扫描速度。
- 明确指定需要的字段,尤其是避免在分页或高频接口中 SELECT *
- 对宽表(字段数 > 30 或含多个大文本字段),按业务场景拆分查询:核心字段走主表,详情字段延迟加载
- 在 MyBatis 或 JPA 中,用 ResultMap 或 Projection 显式约束返回结构
合理控制分页深度
OFFSET 越大,数据库越要跳过前面大量数据,性能呈线性下降。例如 OFFSET 100000 LIMIT 20 在千万级表上可能秒变慢查询。
- 用游标分页(Cursor-based Pagination):基于上一页最后一条记录的有序字段(如 created_at、id)做条件查询,如 WHERE id > 12345 ORDER BY id LIMIT 20
- 限制前端可访问的最大页码(如只允许查前 100 页),后端校验并拒绝非法 offset
- 对统计类分页(如“共 XXX 条”),考虑用估算值(如 EXPLAIN 行数 + 缓存)替代精确 count(*)
统一收口 SQL 构建与执行逻辑
分散在各处的手拼 SQL、动态条件拼接、未参数化的字符串插值,极易引发全表扫描、SQL 注入,也难以统一监控和拦截。
- 所有查询走 DAO 层封装,禁止在 Service 或 Controller 中直接 new Statement / executeQuery
- 复杂条件用 QueryDSL、MyBatis-Plus 的 LambdaQueryWrapper 等类型安全方式构建,避免字符串拼接
- 接入慢 SQL 监控中间件(如 Druid、skywalking、Arthas),设置阈值自动告警,并关联到具体代码行号和调用链
不复杂但容易忽略:很多慢查询不是因为 SQL 多难写,而是少了一个 LIMIT、多了一个嵌套循环、或者忘了给时间范围加 WHERE。把防御意识落到每一行代码里,比事后优化更有效。