1. 为什么需要监控MyBatis的SQL执行信息
在开发基于MyBatis的持久层应用时,我们经常会遇到这样的场景:某个查询突然变慢,但不知道是SQL本身的问题还是参数绑定的问题;或者测试环境运行正常的SQL到了生产环境就报错,却难以复现。这时候如果能在控制台看到完整的SQL执行过程,问题排查效率会大幅提升。
我在实际项目中发现,开发阶段开启SQL日志能帮助发现三类典型问题:
- N+1查询问题(通过观察重复执行的SQL语句)
- 不合理的参数类型转换(通过观察PreparedStatement的参数绑定)
- 慢查询(通过执行时间统计)
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 原生MyBatis的日志配置方案
2.1 基于Log4j的配置实现
MyBatis默认使用SLF4J作为日志门面,我们可以通过log4j.properties文件控制SQL日志输出。以下是一个典型配置:
properties复制# 设置MyBatis接口所在的包为DEBUG级别
log4j.logger.com.example.mapper=DEBUG
# 显示SQL语句(包含参数)
log4j.logger.java.sql.Connection=DEBUG
log4j.logger.java.sql.Statement=DEBUG
log4j.logger.java.sql.PreparedStatement=DEBUG
这种配置会输出类似如下的日志:
code复制DEBUG [main] - ==> Preparing: SELECT * FROM users WHERE id = ?
DEBUG [main] - ==> Parameters: 1(Integer)
DEBUG [main] - <== Total: 1
注意:在生产环境要谨慎开启DEBUG日志级别,可能造成日志量暴增。建议通过环境变量动态控制日志级别。
2.2 日志格式的深度定制
如果需要更结构化的日志输出,可以创建自定义的Log4j Layout:
java复制public class SqlLogLayout extends PatternLayout {
private static final String SQL_PATTERN = "执行方法: %M | SQL: %sql | 耗时: %executionTime ms";
@Override
public String format(LoggingEvent event) {
// 解析MyBatis日志事件并格式化输出
}
}
然后在log4j配置中指定:
properties复制log4j.appender.stdout.layout=com.example.SqlLogLayout
3. 使用第三方插件增强日志功能
3.1 MyBatis Log Free插件原理分析
这款IDEA插件通过拦截MyBatis的StatementHandler实现SQL日志增强。其核心工作原理是:
- 通过Java Agent机制在运行时修改MyBatis的StatementHandler字节码
- 在执行前后插入日志记录逻辑
- 格式化输出包含执行时间、参数等完整信息
安装后无需任何配置即可在IDEA控制台看到如下格式的日志:
code复制[MyBatis Log] method: UserMapper.selectById
[MyBatis Log] sql: SELECT * FROM user WHERE id = 1
[MyBatis Log] time: 12ms
3.2 性能敏感的日志采样方案
在高并发场景下,全量日志可能影响性能。我们可以基于Guava的RateLimiter实现采样日志:
java复制public class SamplingInterceptor implements Interceptor {
private final RateLimiter limiter = RateLimiter.create(10.0); // 每秒10条
@Override
public Object intercept(Invocation invocation) {
if (limiter.tryAcquire()) {
long start = System.currentTimeMillis();
Object result = invocation.proceed();
long time = System.currentTimeMillis() - start;
log.debug("执行方法: {} | 耗时: {}ms",
invocation.getMethod().getName(), time);
return result;
}
return invocation.proceed();
}
}
4. 生产环境下的最佳实践
4.1 动态日志级别控制
结合Spring Cloud Config可以实现运行时动态调整日志级别:
java复制@RefreshScope
@Configuration
public class LoggingConfig {
@Value("${mybatis.log.level:INFO}")
private String logLevel;
@PostConstruct
public void init() {
LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();
context.getLogger("org.mybatis").setLevel(Level.valueOf(logLevel));
}
}
4.2 慢SQL监控告警方案
通过MyBatis插件+Prometheus实现慢SQL监控:
java复制public class SlowSqlMonitor implements Interceptor {
private static final Histogram sqlDuration = Histogram.build()
.name("mybatis_sql_duration_seconds")
.help("SQL execution time distribution")
.register();
@Override
public Object intercept(Invocation invocation) {
Timer.Context timer = sqlDuration.startTimer();
try {
return invocation.proceed();
} finally {
double elapsed = timer.stop();
if (elapsed > 1.0) { // 超过1秒视为慢查询
log.warn("Slow SQL detected: {}", invocation.getMethod());
}
}
}
}
配合Grafana可以绘制出SQL执行时间的P99、P95等指标。
5. 常见问题排查指南
5.1 日志不输出的典型原因
- 日志级别冲突:检查是否有多个日志框架冲突(如同时存在log4j和logback)
- 包路径错误:确保日志配置中的包名与Mapper接口包名一致
- 阈值过滤:某些日志框架有默认的阈值过滤规则
5.2 参数显示为问号的问题
这通常是因为JDBC驱动没有正确实现PreparedStatement的toString方法。解决方案:
- 使用P6Spy等JDBC代理工具
- 升级JDBC驱动版本
- 在MyBatis配置中开启
logImpl=STDOUT_LOGGING
5.3 执行时间统计不准确的情况
当遇到以下情况时,执行时间统计可能失真:
- 使用了MyBatis二级缓存
- 方法内部有多个SQL语句
- 启用了批处理模式
这时应该考虑使用APM工具(如SkyWalking)获取更精确的耗时统计。
