推荐优先使用 Executor 层拦截实现 SQL 性能监控,因其覆盖全链路、上下文丰富;需通过 @Intercepts 声明拦截规则、intercept 中记录耗时并提取 SQL、注册为 Spring Bean;应设慢 SQL 阈值告警、结构化日志输出,并避免插件中执行耗时操作。

直接用 MyBatis 插件机制实现 SQL 执行性能监控,核心是拦截关键执行节点、记录时间戳、提取 SQL 信息并输出或上报。它不改业务代码,也不依赖数据库日志,属于应用层可观测性的轻量方案。
选对拦截点:Executor 还是 StatementHandler?
两者都可行,但用途有侧重:
-
Executor 层拦截(推荐用于全链路耗时):拦截
query和update方法,覆盖所有 SQL 操作,能拿到MappedStatement、参数对象、结果处理器等上下文,适合统计“从 MyBatis 接口调用到结果返回”的完整耗时。 -
StatementHandler 层拦截(推荐用于纯 SQL 执行耗时):拦截
prepare或query方法,更贴近 JDBC 层,能准确捕获预编译、参数绑定、实际执行阶段的耗时,但需手动解析 BoundSql 获取最终 SQL。
多数生产场景建议优先用 Executor 拦截,逻辑清晰、上下文丰富、适配度高。
核心代码结构:三步完成拦截逻辑
一个标准插件类只需实现 Interceptor 接口,并配合 @Intercepts 注解声明拦截规则:
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
立即学习“Java免费学习笔记(深入)”;
-
声明拦截目标:用
@Signature明确 type(如Executor.class)、method(如"query")、args(方法签名参数类型数组);可同时声明多个@Signature覆盖增删改查。 -
重写 intercept 方法:在
invocation.proceed()前后记录系统时间,差值即为执行耗时;通过invocation.getArgs()提取MappedStatement,再调用getBoundSql().getSql()获取带占位符的 SQL,结合参数对象可进一步格式化(如用org.apache.ibatis.scripting.defaults.DefaultParameterHandler渲染)。 -
注册插件:Spring Boot 中加
@Bean返回插件实例;传统 XML 配置则在<plugins>标签下配置。
增强实用性:加阈值告警与结构化输出
基础耗时统计只是起点,真正可用的监控需支持分级响应:
-
慢 SQL 阈值判断:定义
slowThresholdMs = 500,耗时超过即打 WARN 日志,并附上 Mapper 方法全限定名(ms.getId())、SQL 片段、参数摘要(避免敏感信息)。 -
结果集规模预警:在
intercept返回后,若返回值是List或Cursor,可获取 size 并记录,超阈值(如 >10000)单独标记。 -
结构化日志输出:不用
System.out,用 SLF4J 的logger.debug()或logger.warn(),字段对齐(如[SQL] method=com.x.UserMapper.list | sql=SELECT * FROM user WHERE id=? | params=[123] | time=482ms | rows=1),方便 ELK 或日志平台提取。
避坑提醒:别忽略这些细节
看似简单,实操中容易踩坑:
- 插件类必须有无参构造,且不能依赖未初始化的 Spring Bean(如
@Autowired的 service)——建议通过setProperties传入配置,或用ApplicationContextAware延迟获取。 - 不要在
intercept中做耗时操作(如远程调用、文件写入),否则会拖慢主线程;异步上报需用线程池隔离。 - 多插件共存时注意顺序:
Plugin.wrap()是层层代理,先注册的插件在外层,后注册的在内层;耗时统计插件应尽量靠外,避免被其他插件干扰计时。 - MyBatis-Plus 用户可直接启用
PerformanceInterceptor,配置maxTime和format即可开箱使用,适合快速验证。


















