P42 怎么记录 MyBatis 的 SQL 耗时时间
面试题:项目里怎么监控 MyBatis 每一条 SQL 的执行耗时?
方案一:MyBatis 拦截器(Interceptor,推荐)
MyBatis 的插件机制可以拦截四大对象的方法:
Executor(执行器:update/query/commit)StatementHandler(SQL 语句处理)ParameterHandler(参数)ResultSetHandler(结果集)
统计耗时通常拦截 Executor.query/update:
java
@Intercepts({
@Signature(type = Executor.class, method = "query",
args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
@Signature(type = Executor.class, method = "update",
args = {MappedStatement.class, Object.class})
})
public class SqlCostInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
long start = System.currentTimeMillis();
try {
return invocation.proceed();
} finally {
long cost = System.currentTimeMillis() - start;
MappedStatement ms = (MappedStatement) invocation.getArgs()[0];
String sql = ms.getBoundSql(invocation.getArgs()[1]).getSql();
log.info("SQL 耗时 {}ms: {}", cost, sql);
}
}
}配置进 MyBatis:
yaml
mybatis:
configuration:
# 或在配置类里 mybatis-plus 用 @Bean Interceptor扩展:能拿到 Mapper 方法 ID(ms.getId())、参数,可做慢 SQL 告警(超阈值记录/通知)。
方案二:MyBatis-Plus 内置性能分析插件
MyBatis-Plus 提供 PerformanceInterceptor(旧版)/ MybatisPlusInterceptor,能输出每条 SQL 及耗时,开发环境用:
java
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PerformanceInterceptor()); // 旧版本
return interceptor;
}注意:性能分析插件有开销,生产慎用,可做成开关。
方案三:数据库层慢查询日志
- MySQL 开
slow_query_log,long_query_time设置阈值; - 从数据库侧兜底发现慢 SQL(与业务代码解耦)。
方案四:APM 工具
- SkyWalking、Arthas(
trace命令)、Druid 监控(StatFilter输出 SQL 统计)等,生产环境更常用。
加分点
- 拦截器原理:MyBatis 用 JDK 动态代理包装四大核心对象,插件就是代理链;
- 只拦截慢的:先比较耗时再打日志,避免每条 SQL 都打日志影响性能;
- 参数脱敏:日志里别把 SQL 参数原样打出来。
一句话总结
自定义 MyBatis Interceptor 拦截 Executor 的 query/update,在 proceed() 前后记录耗时并输出 SQL/方法 ID,超过阈值告警;开发环境可用 MyBatis-Plus 性能插件,生产再配合 MySQL 慢查询日志和 APM。