生产环境慢 SQL 排查与优化

一、如何快速发现慢 SQL?

1.1 从日志入手(推荐使用链路追踪)

现代微服务架构下,链路追踪系统基本是标配。

  • 推荐工具
diff 复制代码
-   SkyWalking
diff 复制代码
-   Zipkin
-   Jaeger
-   阿里云 ARMS / 火山引擎 APM

定位技巧:

在链路追踪系统里,筛选耗时异常的请求(如 > 1s),然后查看是否是数据库操作耗时过长,通常慢 SQL 一目了然。

1.2 数据库慢查询日志

对于 MySQL,开启慢查询日志是必须的:

sql

sql 复制代码
sql
 体验AI代码助手
 代码解读
复制代码
SHOW VARIABLES LIKE 'slow_query_log';
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 1; -- 超过1秒记录

慢查询日志文件在 /var/lib/mysql/ 或配置文件中指定路径。

工具推荐:

  • mysqldumpslow
  • pt-query-digest(Percona Toolkit)

它们可以快速统计出频繁的慢 SQL 和平均耗时。


二、如何定位具体哪条 SQL 慢?

2.1 应用日志 = 黄金信息源

养成在日志中打印完整 SQL 语句的习惯(带参数的),比如使用 MyBatis 的日志插件:

xml

xml 复制代码
xml
 体验AI代码助手
 代码解读
复制代码
<configuration>
  <logger name="com.yourapp.mapper" level="DEBUG"/>
</configuration>

或者使用:

java

lua 复制代码
lua
 体验AI代码助手
 代码解读
复制代码
log.debug("Executing SQL: {}", boundSql.getSql());

提示:如果你用的是 Spring Boot + MyBatis Plus,开启 SQL 日志只需:

yaml

yaml 复制代码
yaml
 体验AI代码助手
 代码解读
复制代码
mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

三、SQL 优化的核心思路

我们拿到慢 SQL 后,优化一般遵循这几个步骤:

3.1 执行计划 EXPLAIN

sql

ini 复制代码
ini
 体验AI代码助手
 代码解读
复制代码
EXPLAIN SELECT * FROM orders WHERE user_id = 123456 AND status = 'PAID';

关注几个关键字段:

字段 说明
type 联接类型(ALL 是最差的,最好是 const / ref / index)
rows 扫描的行数,越少越好
key 使用的索引
Extra 是否有 Using filesort / Using temporary

如果你看到 Using filesortUsing temporary,那基本可以确定这条 SQL 有优化空间。

3.2 检查索引命中情况

确保 WHERE 条件字段建立了合适的索引

避免在索引字段上做函数或者类型转换(会导致索引失效)

比如这条慢 SQL:

sql

sql 复制代码
sql
 体验AI代码助手
 代码解读
复制代码
SELECT * FROM orders WHERE DATE(create_time) = '2023-11-11';

优化方式是改写为范围比较:

sql

sql 复制代码
sql
 体验AI代码助手
 代码解读
复制代码
SELECT * FROM orders 
WHERE create_time >= '2023-11-11 00:00:00' 
  AND create_time < '2023-11-12 00:00:00';

3.3 避免 SELECT *

取你需要的字段就好,尤其是大表。减少传输的数据量。


四、Java 开发常见的 SQL 性能坑

4.1 N+1 查询问题(MyBatis 常见)

java

ini 复制代码
ini
 体验AI代码助手
 代码解读
复制代码
List<Order> orders = orderMapper.selectByUserId(userId);
for (Order o : orders) {
    o.setItems(itemMapper.selectByOrderId(o.getId()));
}
  • 每个订单查一次明细,100个订单 → 100次 SQL
  • 解决方式:一次性批量查,或使用 MyBatis 的 @ResultMap 联表映射

4.2 分页陷阱:高 offset 慢如狗

sql

vbnet 复制代码
vbnet
 体验AI代码助手
 代码解读
复制代码
SELECT * FROM orders ORDER BY id LIMIT 100000, 10;
  • offset 越大,数据库扫描越多
  • 优化方式:使用游标分页覆盖索引分页

sql

sql 复制代码
sql
 体验AI代码助手
 代码解读
复制代码
SELECT * FROM orders 
WHERE id > last_id 
ORDER BY id ASC 
LIMIT 10;

五、实战案例分享:一个真实的慢 SQL 优化案例

背景

  • 接口响应慢,链路追踪发现 SQL 耗时 4.3s
  • 原始 SQL:

sql

sql 复制代码
sql
 体验AI代码助手
 代码解读
复制代码
SELECT * FROM user_login_log 
WHERE user_id = 123456 
ORDER BY login_time DESC 
LIMIT 1;

问题点

  • user_login_log 有 5000 万条数据
  • user_id 没有索引,ORDER BY + LIMIT 导致全表排序

优化方案

  1. 添加联合索引:

sql

scss 复制代码
scss
 体验AI代码助手
 代码解读
复制代码
CREATE INDEX idx_user_login_time 
ON user_login_log(user_id, login_time DESC);
  1. 执行计划变更,查询耗时降为 8ms

相关推荐
悟能不能悟2 小时前
jasper里面$F和$P的区别
开发语言·后端
短剑重铸之日2 小时前
《7天学会Redis》Day 3 - 持久化机制深度解析
java·redis·后端·缓存
萧曵 丶2 小时前
Spring 全套高频面试题(由浅到深 完整版)
java·后端·spring
武子康2 小时前
大数据-213 Python 手写 K-Means 聚类实战(鸢尾花 Iris 数据集):从距离函数到迭代收敛与坑点
大数据·后端·机器学习
神奇小汤圆2 小时前
MySQL大事务的Recovery优化
后端
魔术师卡颂2 小时前
提问量暴跌 80% ,Stack Overflow 却赚翻了?
前端·后端·ai编程
FAFU_kyp3 小时前
Rust 字符串与切片
开发语言·后端·rust
Java水解3 小时前
Nginx 配置文件完全指南
后端·nginx
好想来前端3 小时前
私有化部署 LLM 时,别再用 Nginx 硬扛流式请求了 —— 推荐一个专为 vLLM/TGI 设计的高性能网关
后端·架构·github