性能排查:SQL 日志、慢查询定位与批处理
接口慢了先别猜,链路是:看到真实 SQL → 找到慢在哪 → 用索引/批处理解决。MyBatis 场景里对应三步:打 SQL 日志、查慢查询、开批处理。
一、让 MyBatis 把 SQL 打出来
日志实现可全局配置(logImpl 支持 SLF4J、LOG4J2、STDOUT_LOGGING 等,未配置时按 classpath 自动发现,以官方文档为准):
<settings>
<setting name="logImpl" value="SLF4J"/>
</settings>
Spring Boot 更常用的两种方式:
mybatis:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 控制台直出
logging:
level:
com.demo.mapper: debug # 或只把 Mapper 包调到 debug
典型输出三段:Preparing: SELECT * FROM user WHERE id = ?、Parameters: 1(String)、Total: 1。Preparing 就是真实执行的 SQL,直接复制去做 EXPLAIN。
二、慢查询定位
数据库侧先把慢 SQL 捞出来:MySQL 开 slow_query_log、设 long_query_time,日志里就是候选优化对象(配置以 MySQL 官方文档为准)。拿到慢 SQL 后用 EXPLAIN 分析:
EXPLAIN SELECT u.id, u.name, COUNT(o.id)
FROM user u LEFT JOIN `order` o ON o.user_id = u.id
WHERE u.status = 1 GROUP BY u.id;
重点看四样:type(出现 ALL 即全表扫,应到 range/ref/eq_ref)、key(是否走索引)、rows(扫描行数)、Extra(Using filesort / temporary 是优化信号)。缺索引就补,复合索引要结合 WHERE 与 ORDER BY 一起设计(细节以数据库官方文档为准)。
三、批处理
循环单条 insert 是"慢"重灾区,每条 SQL 都一次网络往返。MyBatis 提供批量执行器:
// 原生 API:批量执行器
try (SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH)) {
UserMapper mapper = session.getMapper(UserMapper.class);
for (User u : users) {
mapper.insert(u); // 先攒在内存,不逐条提交
}
session.commit(); // flushStatements 统一发出
}
Spring 中换执行器:new SqlSessionTemplate(sqlSessionFactory, ExecutorType.BATCH)。三个注意点:
- 同一批次内先插后查要小心,BATCH 模式下查询会先 flush 缓存,批量 id 回填行为以驱动为准;
- update 返回的影响行数在真正 flush 前不可靠,别拿它做业务判断;
- 攒到一定条数主动 flushStatements 一次,防止内存与单条 SQL 过大。
最后记得 MySQL 驱动侧开 rewriteBatchedStatements=true(JDBC URL 参数),否则"假批处理"仍是一条条发。MyBatis-Plus 的 saveBatch 底层同样依赖批处理执行器与驱动支持(以官方文档为准)。
排查口诀:日志里抄 SQL → EXPLAIN 看索引 → 循环写改批处理。慢的不是框架,是"看不见的 SQL 和一条条发的 INSERT",把这三步做成习惯即可。