项目中如何实现 SQL 耗时监控?(实现篇)

SpringBoot集成p6spy监控sql耗时 本文介绍p6spy的使用场景及用法,对于IDE中直接查看sql耗时及拿取完整的sql语句很有帮助,让我们拿下它。 阅读详情

项目开发中,经常会对耗时较长的 SQL 进行针对性优化,而在搭建项目雏形时,统计 SQL 耗时的能力,是项目中必不可少的基础服务能力。

那么,大家平时在项目中,都是怎么实现 SQL 耗时监控的呢?

本次结合实际项目,去看看如何实现 SQL 耗时监控?当下的绝大多数项目还是主要基于 MyBatis 技术栈完成 CRUD,接下来主要基于 MyBatis 技术栈去谈实现。

1

关键位置,横插一刀,学名:拦截器。

倘若要统计 Mybatis 数据库操作的耗时,那么需要人为指定拦截 Executor 类,因为在 Mybatis 中 SQL 的操作都是从 Executor 类开始的。

上面代码截图,主要是要告诉 MyBatis,当前插件用来拦截哪个对象的哪个方法。其中 type 指定要拦截的对象,例如 Excutor;method 指定要拦截的方法,例如要拦截 update、query 方法;args 代表传递的参数。

接下来就实现 MyBatis 提供的 Interceptor 接口的方法,代码截图如下。

重点关注上图中的标注 1 部分,拦截目标对象的目标方法的执行。此时便可以通过 MappedStatement 对象获取 xml 映射文件中的每一条 SQL 语句,进而完成 SQL 耗时的监控,当然可以进行美化 SQL 输出。

其余代码不解释,为了助你实现 SQL 监控功能,完整代码呈上。

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Properties;
import java.util.regex.Pattern;


import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.type.TypeHandlerRegistry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


@Intercepts({ @Signature(type = Executor.class, method = "update", args = { MappedStatement.class,
                                                                            Object.class }),
              @Signature(type = Executor.class, method = "query", args = { MappedStatement.class,
                                                                           Object.class,
                                                                           RowBounds.class,
                                                                           ResultHandler.class }) })
public class MybatisSqlLoggingInterceptor implements Interceptor {


  private static Logger logger = LoggerFactory.getLogger("db_file");


    private static final Pattern WHITE_SPACE_BLOCK_PATTERN = Pattern
        .compile("([\\s]{2,}|[\\t\\r\\n])");


    public Object intercept(Invocation invocation) throws Throwable {
        MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
        Object parameter = null;
        if (invocation.getArgs().length > 1) {
            parameter = invocation.getArgs()[1];
        }
        String sqlId = mappedStatement.getId();
        BoundSql boundSql = mappedStatement.getBoundSql(parameter);
        Configuration configuration = mappedStatement.getConfiguration();
        Object returnValue = null;
        long start = System.currentTimeMillis();
        // 执行目标方法
        returnValue = invocation.proceed();
        // 统计 SQL 耗时
        long end = System.currentTimeMillis();
        long time = end - start;
        
        // 打印日志
        logSqlInfo(configuration, boundSql, sqlId, time);


        return returnValue;
    }


    public static void logSqlInfo(Configuration configuration, BoundSql boundSql, String sqlId,
                                  long time) {


        String sqlAndParams = showSqlAndParams(configuration, boundSql);


        StringBuilder str = new StringBuilder(100);
        str.append(sqlId);
        str.append(":");
        str.append(sqlAndParams);
        str.append(":");
        str.append(time);
        str.append("ms");


        String line = str.toString();


        logger.info(line);
    }


    private static String getParameterValue(Object obj) {
        String value = null;
        if (obj instanceof String) {
            value = "'" + obj.toString() + "'";
        } else if (obj instanceof Date) {
            value = "'" + DateFormatUtils.ISO_DATETIME_TIME_ZONE_FORMAT.format(new Date()) + "'";
        } else {
            if (obj != null) {
                value = obj.toString();
            } else {
                value = "";
            }


        }
        return value;
    }


    public static String showSqlAndParams(Configuration configuration, BoundSql boundSql) {
        Object parameterObject = boundSql.getParameterObject();
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();


        String sql = WHITE_SPACE_BLOCK_PATTERN.matcher(boundSql.getSql()).replaceAll(" ");


        List<String> params = new ArrayList<>();


        if (parameterMappings.size() > 0 && parameterObject != null) {
            TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
            if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
                params.add(getParameterValue(parameterObject));
            } else {
                MetaObject metaObject = configuration.newMetaObject(parameterObject);
                for (ParameterMapping parameterMapping : parameterMappings) {
                    String propertyName = parameterMapping.getProperty();
                    if (metaObject.hasGetter(propertyName)) {
                        Object obj = metaObject.getValue(propertyName);
                        params.add(getParameterValue(obj));
                    } else if (boundSql.hasAdditionalParameter(propertyName)) {
                        Object obj = boundSql.getAdditionalParameter(propertyName);
                        params.add(getParameterValue(obj));
                    }
                }
            }
        }


        return sql + ":[" + StringUtils.join(params, ',') + "]";
    }


    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }


    @Override
    public void setProperties(Properties arg0) {
        // just ignore
    }
}

代码编写完成,最后别忘记把写好的插件,注册到全局配置文件 mybatis-config.xml 中。

<!-- 插件配置 -->
<plugins>
     <plugin interceptor="org.yiyuanxiaojiangV5.common.persistence.interceptor.MybatisSqlLoggingInterceptor" />
</plugins>

2

抓住关键位置,就可以为所欲为,学名:打蛇打七寸。

经过上面代码及配置,你若直接集成到项目中,让 SQL 以及耗时情况输出到日志文件,就不再是个难题。

既然知道在哪儿控制 SQL 耗时,那就可以做的更多,如果 SQL 执行耗时超过 5 秒,可以接入报警模块,进行邮件、短信、QQ、微信等进行实时报警。

可以把上图标注 2 的打印 SQL 耗时日志的部分,换成报警通知模块接入,让 SQL 耗时进行实时报警成为现实。

3

学以致用,大家先用明白再说。

本次聊个皮毛,贵在谈使用思路,至于原理等大家用明白了,一起带你钻进源码来个系列长谈。

一起聊技术、谈业务、喷架构,少走弯路,不踩大坑。会持续输出原创精彩分享,敬请期待!

推荐阅读:

真实|为什么要做有灵魂的程序员?

Java 如何实现优雅停服?刨根问底

MDC是什么鬼?用法、源码一锅端

520程序猿用最时尚的方式说爱你

sql耗时监控与sql语句日志输出 日志监控插件p6spy的使用 阅读详情

相关推荐

SkyWalking java-agent 是如何工作的,自己实现一个监控sql执行耗时的agent

Java Agent 是 Java Instrumentation API 的一个强大功能,它允许你在运行时修改 Java 应用程序的行为。它们通常用于性能分析、监控和修改应用程序的执行。 Apache SkyWalking 是一个开源的应用性能监控 (APM) 工具,支持分布式系统的追踪、监控和诊断。SkyWalking Agent 是其中的一个重要组件,用于在服务端应用中收集性能数据和追踪信息,并将其发送到 SkyWalking 后端服务器进行处理和展示。

zfj321的博客 1535

mybatis插件_MyBatis插件-慢sql耗时监控插件

很多情况我们系统要分析出来那些查询很慢,其实可以通过mybatis开发一个插件,去监控每次执行的sql耗时,如果超过配置的时间,则认为该sql属于慢sql需要优化。日志里面就会打印每一个sql及相应的时间消耗需要在mybatis.xml配置中标签下增加自己的插件代码如下 最后附上SqlPerformanceInterceptor.java拦截器代码/** * @author 郏高阳 * @...

weixin_39597262的博客 729

SQL耗时统计方法

SQL耗时统计方法方案1:AOP方案2:Mybatis插件方案3:使用连接池内置的监控 方案1:AOP @Aspect @Component @Slf4j public class MapperAspect { @AfterReturning("execution(* cn.xbmchina.mybatissqltime.mapper.*Mapper.*(..))") public void logServiceAccess(JoinPoint joinPoint) { log.info(

04stone37 1790

监控Hibernate执行SQL

 几乎 80% - 85% 的数据库性能问题是由于应用数据库的设计或者应用程序本身的代码所引起的。因此良好的事务处理能力需要在设计应用程序的时候,在设计数据库的时候就考虑到性能和伸缩性。          在我们使用Java EE开发企业级应用程序的过程中,总会涉及到系统的性能问题,并且都会与数据库进行打交道。当我们碰到数据库性能优化时,最有效的就是直接跟踪SQL 语句的执行情况,

xmsea的专栏 3162

MyBatis插件开发:实现SQL耗时监控与性能优化

MyBatis作为Java生态中广泛使用的ORM框架,其插件机制基于JDK动态代理实现,通过拦截Executor、StatementHandler等核心组件,为开发者提供了强大的扩展能力。这种机制在性能监控、SQL审计等场景下尤为实用,可以实现无侵入式的数据库监控方案。通过实现Interceptor接口,开发者可以精确统计SQL执行耗时,识别慢查询,并结合Prometheus等监控工具实现可视化分析。在实际工程实践中,这种技术方案既能有效诊断数据库性能瓶颈,又不会对原有业务代码造成侵入性修改,是提升系统稳定

weixin_30268921的博客 409

SpringBoot中使用AOP 监控sql耗时

SpringBoot中AOP 监控sql耗时

Eacter的专栏 4393

可观测性-Metrics-慢SQL监控

利用Mybatis拦截器实现

laker的博客 679

56-FMC连接器电路设计

FMC连接器电路设计

weixin_66510961的博客 4723

优雅的利用Mybatis插件实现sql查询耗时统计

优雅的利用Mybatis插件实现sql查询耗时统计 一. Mybatis反射机制讲解 二. 代理模式讲解 静态代理 动态代理 JDK动态代理参考代码 Proxy.newProxyInstance(xxx.getClass().getClassLoader(), new Class[]{Interface.class}, new InvocationHandler(){ public Object invoke(Obj

zhangvb521的博客 1754

CATIA_DELMIA_V5R2019安装包下载及安装教程破解

CATIA_V5R2019/DELMIA_V5R2019安装包下载及安装教程破解

一只机械工装熊的博客 8350

博途PLC PID仿真LSim_PT1库算法(一阶系统差分方程推导全过程)

博途PLC的PID仿真可以参看下面这篇博客,这篇文章我们主要论坛仿真库LSim的算法由来。如何利用博途PLC PID_Compact 进行PID仿真(一阶系统)_RXXW_Dor的博客-优快云博客博途PLC PID控制器 PID_Compact FB如何使用可以参看下面这篇文章,这里不再赘述。

RXXW_Dor的博客 2858

如何实现数据监控_项目中如何实现 SQL 耗时监控?(实现篇)

项目开发中,经常会对耗时较长的 SQL 进行针对性优化,而在搭建项目雏形时,统计 SQL 耗时的能力,是项目中必不可少的基础服务能力。那么,大家平时在项目中,都是怎么实现 SQL 耗时监控的呢?本次结合实际项目,去看看如何实现 SQL 耗时监控?当下的绝大多数项目还是主要基于 MyBatis 技术栈完成 CRUD,接下来主要基于 MyBatis 技术栈去谈实现。1关键位置,横插一刀,学名:拦截器。...

weixin_39611722的博客 376

10、基于单倍型多样性隐马尔可夫模型的基因型错误检测

本文提出了一种基于单倍型多样性隐马尔可夫模型(HMM)的基因型错误检测方法,旨在提升三人组基因型数据中错误识别的准确性。通过利用群体水平的连锁信息,该方法克服了传统方法在小系谱数据中表现不佳的局限性。文章详细介绍了模型结构、三种替代似然函数及其计算方法,并通过模拟数据集和真实数据集验证了方法的有效性。实验结果显示,该方法在灵敏度、特异度和准确率方面均表现出色,为基因数据质量控制提供了新的解决方案。

moon9的博客 96

sql如何实现明细账_项目中如何实现 SQL 耗时监控?(实现篇)

项目开发中,经常会对耗时较长的 SQL 进行针对性优化,而在搭建项目雏形时,统计 SQL 耗时的能力,是项目中必不可少的基础服务能力。那么,大家平时在项目中,都是怎么实现 SQL 耗时监控的呢?本次结合实际项目,去看看如何实现 SQL 耗时监控?当下的绝大多数项目还是主要基于 MyBatis 技术栈完成 CRUD,接下来主要基于 MyBatis 技术栈去谈实现。1关键位置,横插一刀,学名:拦截器。...

weixin_42120541的博客 166

【GlobalMapper精品教程】038:模拟水位上升(洪水淹没分析)案例教程

基于数字高程模型 ( DEM )格网模型,实现给定水深情况下洪水淹没区的计算模型,讨论洪水淹没演进过程可视化实现的关键技术,以三维可视化方式,动态而形象地模拟在指定洪水水位下的洪水淹没演进过程。

智慧空间实验室 2884

镜头像差之五——场曲

什么是场曲? 场曲[curvature of field] 物面上所有的点经过系统后,最佳焦点位置不在同一平面上,若这些像点所成的面为曲面,则称为系统有场曲。 与物高二次方、入射光瞳口径一次方成正比的像差。若仅存在场曲,则所有物平面上的点都有相应的像点,但分布在一个球面上;若采用弯成此种形状的底片,则可获得处处清晰的像。此时在理想像平面上,像点呈现为圆斑。 第一张图是最简单也是最容易理解的场曲,也就是对焦点锐利,其余部分逐渐松散,但实际上大多现代镜头的场曲都呈现为图二的形态:对焦点与边缘锐度.

camerall 1万+

MyBatis插件开发-实现SQL执行耗时监控

《MyBatis插件开发实战:从原理到企业级SQL监控实现》 本文系统性地介绍了MyBatis插件开发的全过程。首先通过真实案例说明了慢SQL问题的严重性,然后深入解析MyBatis插件机制,包括其拦截原理和责任链模式实现。文章重点演示了如何开发企业级SQL监控插件,涵盖核心功能如执行耗时统计、慢SQL告警和SQL注入检测。通过性能测试数据表明,优化后的插件性能影响可控制在30%以内。最后总结了插件开发的"五大军规":单一职责、性能优先、配置化、健壮异常处理和兼容性考虑,并提供了生产环境

七夜zippoe的博客 1489

18、知识表示与学习范式

本文探讨了智能系统中的核心组成部分——知识表示与学习范式。从基础知识到实际应用,详细解析了符号表示、框架表示、语义网络和本体表示等知识表示方法,并介绍了监督学习、无监督学习、半监督学习和强化学习等学习范式。此外,还深入分析了这些技术在自然语言处理、图像识别和推荐系统等领域的具体应用及操作步骤。最后,展望了未来可能的技术发展方向。

ttt77的博客 84

MyBatis拦截器精准监控SQL耗时

今天跟大家详细讲了如何用 MyBatis 拦截器实现 SQL 耗时监控,从核心代码编写到配置生效,再到进阶技巧,都是我在实际项目中验证过的有效方案。其实对于后端开发者来说,做好 SQL 监控不是 “额外任务”,而是保障服务稳定的 “基本功”—— 它能帮你提前发现性能隐患,避免线上故障,也能让你在排查问题时少走弯路。最后想呼吁大家:看完这篇文章后,不妨花 30 分钟在自己的项目里试试这个拦截器。如果遇到问题,比如配置不生效、日志打印异常,欢迎在评论区留言,我会尽量帮大家解答;

2501_93873871的博客 530

K8S搭建(centos)完整版

每个人都有惰性,但不断学习是好好生活的根本,共勉!

感谢阅读 4147

IDEA的使用指南

IDEA基于java的使用手册,可以最快速的上手,在使用中不断的丰富自己的使用技巧.

突破Android性能瓶颈:LitePal SQL执行耗时监控与阈值告警实现

在Android应用开发中,数据库操作(SQLite)往往是性能瓶颈的重灾区。用户点击按钮后长时间无响应、列表滑动卡顿、应用ANR(Application Not Responding)等问题,很多都与未优化的SQL操作直接相关。根据Google性能团队的统计,**超过200ms的数据库操作会被用户感知为卡顿**,而主线程中执行超过500ms的SQL操作则可能触发系统ANR。 ### 典型场景痛...

gitblog_00828的博客 631

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

记录MyBatis SQL 耗时记录的拦截器实战

在性能优化的道路上,SQL执行时间往往是关键突破口。本文将从一个八年经验Java开发者的视角,带你深入探索MyBatis SQL耗时监控的最佳实践。

Minoz_wl的博客 1064

霍尼韦尔SoftMaster使用说明

霍尼韦尔SoftMaster使用说明

MyBatis SQL 耗时记录的拦截器实战

谨慎处理参数日志必须进行敏感信息脱敏大对象参数截断处理区分环境配置开发环境:记录详细SQL和参数生产环境:仅记录慢查询,关闭参数打印线程安全考量拦截器是单例的,所有状态变量必须线程安全使用ThreadLocal存储耗时上下文避免过度监控核心业务SQL重点监控低频管理后台SQL可降低监控强度与其他监控工具协同整合链路追踪(如SkyWalking)的TraceID与APM工具协同工作。

2401_89221704的博客 1008

MyBatis SQL执行耗时监控插件开发与实践

SQL性能监控是数据库优化的关键环节,通过拦截器机制可以无侵入式采集执行耗时数据。MyBatis插件基于责任链模式实现方法拦截,能够监控Executor、StatementHandler等核心组件。这种技术方案既能识别慢查询问题,又能建立性能基线,特别适合电商、金融等高并发场景。结合Prometheus和Grafana可实现监控数据可视化,通过采样率和异步日志等优化手段可将性能损耗控制在5%以内。实践中这类方案通常能降低60%以上的平均查询耗时,有效预防数据库连接池耗尽等生产事故。

448
上一篇: 真实|为什么要做有灵魂的程序员?
下一篇: 真实|技术人员该如何站好最后一班岗?
一猿小讲
博客等级 码龄15年 294粉丝 156原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值