查看SQL的执行时间
使用注意
下面的两种方案中,方案1和方案2不能共存,否则会优先执行MyBatisplus自定义的SQL耗时查询拦截器。
方案1.MyBatis 自定义拦截器
原生 mybatis 日志只会打印 SQL,不带耗时,自己写一个 Interceptor 拦截 StatementHandler,拿到开始结束时间,打印毫秒耗时:
java
package com.cghbh.handler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.apache.ibatis.session.ResultHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import java.sql.Connection;
import java.util.Properties;
/**
* MyBatis SQL耗时拦截器,开发调试使用
*/
@Component
@Intercepts({
@Signature(type = StatementHandler.class,
method = "prepare",
args = {Connection.class, Integer.class}),
@Signature(type = StatementHandler.class,
method = "query",
args = {java.sql.Statement.class, ResultHandler.class}),
@Signature(type = StatementHandler.class,
method = "update",
args = {java.sql.Statement.class})
})
public class SqlCostInterceptor implements Interceptor {
private static final Logger log = LoggerFactory.getLogger(SqlCostInterceptor.class);
// 慢SQL阈值,毫秒,超过会黄色警告
private static final long SLOW_SQL_THRESHOLD = 500;
@Override
public Object intercept(Invocation invocation) throws Throwable {
long start = System.currentTimeMillis();
Object proceed;
try {
proceed = invocation.proceed();
} finally {
long cost = System.currentTimeMillis() - start;
// 获取 StatementHandler
StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
MetaObject metaObject = SystemMetaObject.forObject(statementHandler);
MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");
BoundSql boundSql = statementHandler.getBoundSql();
String sql = boundSql.getSql().replaceAll("\\s+", " ");
String mapperId = mappedStatement.getId();
if (cost > SLOW_SQL_THRESHOLD) {
log.warn("\n【慢SQL】mapperId:{} \n耗时: {} ms \nSQL: {}", mapperId, cost, sql);
} else {
log.info("\n【SQL】mapperId:{} \n耗时: {} ms \nSQL: {}", mapperId, cost, sql);
}
}
return proceed;
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
}
}当有请求过来之后,SQL执行和耗时效果如下:

使用总结
✅优点
- 纯代码,不改数据源,切换环境方便
- 可控,可以自己定制日志格式、过滤不需要打印的 SQL
- 不想引入第三方包的时候用
❌ 缺点
- 打印的 SQL 还是带
?,参数是分开的,不能直接复制执行(硬伤!) - 自己写要考虑边界(批量、存储过程等),容易有小 bug
👉 适合:只单纯看耗时,不经常复制 SQL 去数据库执行;不想引入额外依赖。
方案 2.P6Spy
P6Spy 是 JDBC 代理,能打印填充完参数的完整 SQL + 执行耗时,适合 MyBatis 开发调试脚本之家。
引入依赖
在qk-management包中引入这个依赖。
xml
<dependency>
<groupId>p6spy</groupId>
<artifactId>p6spy</artifactId>
<version>3.9.1</version>
</dependency>修改application.yml配置
修改 yml 数据源:driver 换成 com.p6spy.engine.spy.P6SpyDriver,url 开头加 p6spy。
java
#数据库连接信息
spring:
datasource:
driver-class-name: com.p6spy.engine.spy.P6SpyDriver
url: jdbc:p6spy:mysql://localhost:3306/qk
username: root
password: Xianyu@666
# driver-class-name: com.mysql.cj.jdbc.Driver
# url: jdbc:mysql://localhost:3306/qk
#mybatis-plus配置
mybatis-plus:
configuration:
map-underscore-to-camel-case: true # 开启驼峰命名
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 打印sql
global-config:
db-config:
id-type: auto # 全局主键策略 数据库自增
mapper-locations: classpath*:/mappers/*.xml # 映射文件位置增加配置文件
创建spy.properties文件,放到 src/main/resources 下面:
yaml
# 模块:日志模块 + 慢SQL检测模块
modulelist=com.p6spy.engine.logging.P6LogFactory,com.p6spy.engine.outage.P6OutageFactory
# 使用自定义单行模板(重点:executionTime 就是耗时ms)
logMessageFormat=com.p6spy.engine.spy.appender.CustomLineFormat
customLogMessageFormat=【P6Spy】时间:%(currentTime) | 耗时:%(executionTime) ms | 类型:%(category) | 连接ID:%(connectionId) | SQL:%(sql)
# 输出到控制台(开发首选)
appender=com.p6spy.engine.spy.appender.StdoutLogger
# 如果想交给slf4j/logback统一管理,换成下面这行
# appender=com.p6spy.engine.spy.appender.Slf4JLogger
# 日期格式
dateformat=yyyy-MM-dd HH:mm:ss.SSS
# 真实驱动类(mysql8用 com.mysql.cj.jdbc.Driver)
driverlist=com.mysql.cj.jdbc.Driver
# 注销原有驱动
deregisterdrivers=true
useprefix=true
# 排除不需要打印的类别,减少垃圾日志
# 可选值:error,info,batch,debug,statement,commit,rollback,result,resultset
excludecategories=info,debug,result,resultset
# 慢SQL检测开关
outagedetection=true
# 超过 500ms 标记为慢SQL,单位毫秒,不同版本可能不一样,有的是秒,有的是毫秒
outagedetectioninterval=500使用的效果如下,输出补全参数后的完整 SQL(不是 ?,直接复制就能丢 Navicat 执行!这个是最大优势):

使用总结
✅ 优点
- 输出补全参数后的完整 SQL(不是
?,直接复制就能丢 Navicat 执行!这个是最大优势) - 自带 SQL 执行耗时,格式清晰
- 不用写代码,只改依赖 + 数据源配置,侵入很小
- 还能打印执行栈、连接信息
❌ 缺点
绝对不能上生产:有性能损耗- 改数据源 url、driver,切换的时候要小心忘记切回来,容易线上炸
- 依赖多一层 JDBC 代理
👉 适合:写 xml、调分页、联表查询,想直接拿到完整 SQL + 耗时,复制去数据库测试。