一、如何快速发现慢 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/ 或配置文件中指定路径。
工具推荐:
mysqldumpslowpt-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 filesort 或 Using 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 导致全表排序
优化方案
- 添加联合索引:
sql
scss
scss
体验AI代码助手
代码解读
复制代码
CREATE INDEX idx_user_login_time
ON user_login_log(user_id, login_time DESC);
- 执行计划变更,查询耗时降为 8ms