目标记录每次请求内的http、es、mysql耗时,本篇讨论mysql部分

为什么说要探索,这不是很简单的事么?但是能满足以下几点么?

  • 能记录limit等参数
  • 能将参数和sql写一起,能直接使用
  • 能记录耗时
  • 能计数累加,统计一次请求中sql执行的总数和总耗时

spring原生能力

logging.level.org.hibernate.SQL=debug
logging.level.org.hibernate.type.descriptor.sql.BasicBinder=trace

通过上面两条配置。

  • ️可以显示sql.
  • 不能和参数一行显示
  • 不能显示limit参数
  • 不能计数和记录耗时
2021-02-23 19:35:42.932 DEBUG 97586 --- [  restartedMain] org.hibernate.SQL                        : select admin0_.id as id1_0_, admin0_.create_time as create_t2_0_, admin0_.modify_time as modify_t3_0_, admin0_.email as email4_0_, admin0_.password as password5_0_, admin0_.status as status6_0_, admin0_.username as username7_0_ from admin admin0_ where admin0_.username=?
2021-02-23 19:35:42.949 TRACE 97586 --- [ restartedMain] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [VARCHAR] - [root]

原生log+org.hibernate.EmptyInterceptor

org.hibernate.EmptyInterceptor提供钩子,hibernate本身提供entity的curd钩子。重写EmptyInterceptor方法,可以实现计数。但是onPrepareStatement方法只是装配sql前的事件,而且不是完整的sql。

  • ️ 可以显示sql
  • 不能和参数一行显示
  • 不能显示limit参数
  • ️ 能计数
  • 不能记录耗时
spring.jpa.properties.hibernate.ejb.interceptor=com.vison.itdoc.config.HibernateInterceptor
public class HibernateInterceptor extends EmptyInterceptor {

    @Override
public boolean onLoad(Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] types) {
// Log.info("onload...", entity)
return true;
} @Override
public String onPrepareStatement(String string) {
// count++
return INSTANCE.onPrepareStatement(string);
} @Override
public void afterTransactionCompletion(Transaction t) {
INSTANCE.afterTransactionCompletion(t);
Log.info("after trans complete", t);
} }

log4jdbc

log4jdbc能很好的解决sql完整显示和记录耗时的问题

2021-02-23 19:59:13.709  INFO 97586 --- [nio-8081-exec-1] jdbc.sqltiming                           : select posts0_.id as id1_2_, posts0_.create_time as create_t2_2_, posts0_.modify_time as modify_t3_2_,
posts0_.content as content4_2_, posts0_.title as title5_2_ from posts posts0_ where 1=1 order
by posts0_.id asc limit 10 ;
{executed in 1 msec}

还能够定义超过1定时间的执行sql记录为error类型。

        <dependency>
<groupId>com.googlecode.log4jdbc</groupId>
<artifactId>log4jdbc</artifactId>
<version>1.2</version>
<scope>runtime</scope>
</dependency>
spring.datasource.driver-class-name: net.sf.log4jdbc.DriverSpy
#使用log4jdbc后mysql的url
spring.datasource.url=jdbc:log4jdbc:mysql://localhost:3306/xxxx?useUnicode=true&characterEncoding=UTF-8
#使用log4jdbc后oracle的url
#spring.datasource.url: jdbc:log4jdbc:oracle:thin:@127.0.0.1:1521:orcl

注意需要添加spring.datasource.driver-class-name 和更改 spring.datasource.url 将jdbc改为 jdbc:log4jdbc

log4jdbc.properties可以定义更多配置

#配置为需要记录的包或类匹配路径
#log4jdbc.debug.stack.prefix=com.drp
#log4jdbc加载的drivers (驱动名)
#log4jdbc.drivers=oracle.jdbc.OracleDriver
log4jdbc.auto.load.popular.drivers=true
#在日志中显示warn警告
log4jdbc.statement.warn=true
#毫秒值.执行时间超过该值的SQL语句将被记录为warn级别.
log4jdbc.sqltiming.warn.threshold=2000
#毫秒值.执行时间超过该值的SQL语句将被记录为error级别.
log4jdbc.sqltiming.error.threshold=3000
#是把boolean记录为 'true'/'false' 还是 1/0. 默认设置为false,不启用,为了移植性.
#log4jdbc.dump.booleanastruefalse=true
#输出的sql,一行最大的字符数,默认90. 以后新版可能为0
#log4jdbc.dump.sql.maxlinelength=90
#如果在调试模式下转储,则转储整个堆栈跟踪 默认false
log4jdbc.dump.fulldebugstacktrace=false #是否记录某些类型的语句,默认true
log4jdbc.dump.sql.select=true
log4jdbc.dump.sql.insert=true
log4jdbc.dump.sql.delete=true
log4jdbc.dump.sql.update=true
log4jdbc.dump.sql.create=true #输出sql末尾处加入分号,默认false
log4jdbc.dump.sql.addsemicolon=true #将此设置为false以不修剪已记录的SQL
log4jdbc.trim.sql=true
#将此设置为false不删除额外的空行
log4jdbc.trim.sql.extrablanklines=true #log4jdbc.suppress.generated.keys.exception=false
  • ️ 可以显示sql
  • ️ 不能和参数一起显示
  • ️ 不能显示limit参数
  • 能计数
  • ️ 能记录单个sql耗时
  • 不能统计总耗时

不足的是,单纯log4jdbc并不能满足所有。理论上log4jdbc+org.hibernate.EmptyInterceptor可以满足需求了

P6Spy

测试完毕,发现P6Spy目前最能满足需求:

  • ️ 可以显示sql
  • ️ 不能和参数一起显示
  • ️ 不能显示limit参数
  • ️ 能计数
  • ️ 不能记录耗时
  • ️ 支持curd事件前后钩子,钩子参数返回sql和执行耗时及异常信息
        <dependency>
<groupId>p6spy</groupId>
<artifactId>p6spy</artifactId>
<version>3.9.1</version>
</dependency>

log4jdbc需要改driver和url

spring.datasource.driver-class-name=com.p6spy.engine.spy.P6SpyDriver
spring.datasource.url=jdbc:p6spy:mysql://localhost:3306/test?useLegacyDatetimeCode=false&serverTimezone=UTC

psy.properties可以定义更多配置

#modulelist=com.p6spy.engine.spy.P6SpyFactory,com.p6spy.engine.logging.P6LogFactory,com.p6spy.engine.outage.P6OutageFactory
modulelist=com.vison.itdoc.config.CustomeP6Factory,com.p6spy.engine.logging.P6LogFactory,com.p6spy.engine.outage.P6OutageFactory
#moduelist很关键,我这里使用了自定义的Factory,因为我需要自定义event
appender=com.p6spy.engine.spy.appender.Slf4JLogger
logMessageFormat=com.p6spy.engine.spy.appender.CustomLineFormat
customLogMessageFormat=%(executionTime) ms|%(category)|%(sql)
excludecategories=result,resultset,info,debug

正常使用默认配置就可以显示出sql和耗时信息

 4 ms|statement|select admin0_.id as id1_0_, admin0_.create_time as create_t2_0_, admin0_.modify_time as modify_t3_0_, admin0_.email as email4_0_, admin0_.password as password5_0_, admin0_.status as status6_0_, admin0_.username as username7_0_ from admin admin0_ where admin0_.username='root'

可以看到,耗时信息和实际参数

自定义事件

modulelist=com.p6spy.engine.spy.P6SpyFactory改成自定义Factory

自定义Factory

public class CustomeP6Factory implements com.p6spy.engine.spy.P6Factory {

    @Override
public P6LoadableOptions getOptions(P6OptionsRepository optionsRepository) {
return new P6SpyOptions(optionsRepository);
} @Override
public JdbcEventListener getJdbcEventListener() {
return new P6spyListener(); //使用自定义Listener
} }

自定义事件

public class P6spyListener extends JdbcEventListener {

    @Override
public void onAfterExecuteQuery(PreparedStatementInformation statementInformation, long timeElapsedNanos, SQLException e) {
App.sqlCount.incrementAndGet();
Log.info("execute query...", statementInformation.getSqlWithValues());
} @Override
public void onAfterExecuteUpdate(PreparedStatementInformation statementInformation, long timeElapsedNanos, int rowCount, SQLException e) {
App.sqlCount.incrementAndGet();
Log.info("execute update..", statementInformation.getSqlWithValues());
} @Override
public void onAfterExecute(StatementInformation statementInformation, long timeElapsedNanos, String sql, SQLException e) {
Log.info("execute..", statementInformation.getSqlWithValues());
} }

可以看到,我在自定义事件中进行了sql计数.于是我可以在请求结束时打印每次请求的总sql执行次数。

public class RequestInitInterceptor implements HandlerInterceptor {

    public RequestInitInterceptor() {
} @Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
App._uniq_req_no = UUID.randomUUID().toString();
App.sqlCount = new AtomicInteger(0);
Log.setMsgTraceNo(App._uniq_req_no);
Log.info("request start...", handler);
return true;
} @Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
Log.info(String.format("finish request sql执行次数:%s", App.sqlCount));
} }

由于事件参数还给出了timeElapsedNanos,最终我们还能统计出所有sql执行的耗时。这样一来我们就能看出一次请求内,最耗时的操作具体是什么。达到类似以下效果:

参考

spring-boot记录sql探索的更多相关文章

  1. spring boot打印sql语句-mybatis

    spring boot打印sql语句-mybatis 概述 当自己编写的程序出现了BUG等等,找了很久 调试运行了几遍到mapper层也进去调试进了源码,非常麻烦 我就想打印出sql语句,好进行解决B ...

  2. spring boot(二): spring boot+jdbctemplate+sql server

    前言 小项目或者做demo时可以使用jdbc+sql server解决即可,这篇就基于spring boot环境使用jdbc连接sql server数据库,和spring mvc系列保持一致. 在sp ...

  3. spring boot mybatis sql打印到控制台

    如何设置spring boot集成 mybatis 然后sql语句打印到控制台,方便调试: 设置方法: 在application.properties文件中添加: logging.level.com. ...

  4. Spring Boot源码探索——自动配置的内部实现

    前面写了两篇文章 <Spring Boot自动配置的魔法是怎么实现的>和 <Spring Boot起步依赖:定制starter>,分别分析了Spring Boot的自动配置和起 ...

  5. spring boot 自定义sql分页查询

    1.自定义sql查询分页 @Override public <T> Page<T> pageSQL(@Nonnull String sql, @Nonnull Pageable ...

  6. spring boot 打印sql

    配置: logging.level.gov.chinatax.ctims.dao.mapper=DEBUG or logging: level: gov.chinatax.ctims.dao.mapp ...

  7. Spring Boot 集成 MyBatis和 SQL Server实践

    概 述 Spring Boot工程集成 MyBatis来实现 MySQL访问的示例我们见过很多,而最近用到了微软的 SQL Server数据库,于是本文则给出一个完整的 Spring Boot + M ...

  8. 基于Spring Boot,使用JPA调用Sql Server数据库的存储过程并返回记录集合

    在上一篇<基于Spring Boot,使用JPA操作Sql Server数据库完成CRUD>中完成了使用JPA对实体数据的CRUD操作. 那么,有些情况,会把一些查询语句写在存储过程中,由 ...

  9. 基于Spring Boot,使用JPA动态调用Sql查询数据

    在<基于Spring Boot,使用JPA操作Sql Server数据库完成CRUD>,<基于Spring Boot,使用JPA调用Sql Server数据库的存储过程并返回记录集合 ...

随机推荐

  1. C++ part5

    为啥大三了课少了一点点,做作业的时间反而多了一大堆堆??? 关于protect 只能被本类或者子类的成员函数,或者友元函数访问. 友元函数: #include <iostream> #in ...

  2. 2021-2-16:请问你知道分布式设计模式中的Quorum思想么?

    有效个数(Quorum) 有效个数(Quorum)这个设计模式一般是指分布式系统的每一次修改都要在大多数实例上通过来确定修改通过. 问题背景 在一个分布式存储系统中,用户请求会发到一个实例上.通常在一 ...

  3. codepen 上25个最受欢迎的HTML/CSS代码

    Codepen是一个非常了不起的网站,优设哥在设计师网址导航上也大力推荐过,得到了很多同学的喜爱,也是全球web前端开发人员的圣地! 我搜索了一些时下最好最流行的codepen(仅限于HTML和CSS ...

  4. How to get the real screen size(screen resolution) by using js

    How to get the real screen size(screen resolution) by using js 获取用户屏幕的真实像素分辨率, 屏幕实际尺寸 window.deviceP ...

  5. React Hooks: useState All In One

    React Hooks: useState All In One useState import React, { useState } from 'react'; function Example( ...

  6. Keep Fitness

    Keep Fitness 健身 keep health 训练流程 Part 1 热身 5-10分钟 Part 2 肌肉力量训练 30分钟 大肌群包括:胸.背.腿.臀: 小肌群包括:肩.二头肌.三头肌. ...

  7. Python Learning Paths

    Python Learning Paths Python Expert Python in Action Syntax Python objects Scalar types Operators St ...

  8. Dart 编写Api弃用警告

    例如body2在以后的版本将被bodyText1代替 @Deprecated( 'This is the term used in the 2014 version of material desig ...

  9. 创建gitHub账户并配置秘钥

    1. 登录注册地址 https://github.com/ 2.点击注册 Sign up 3.输入邮箱 密码 进行注册 4.注册成功后,登录邮箱验证 .然后通过邮箱和密码登录gitHub.设置 set ...

  10. [Python] 基于 jieba 的中文分词总结

    目录 模块安装 开源代码 基本用法 启用Paddle 词性标注 调整词典 智能识别新词 搜索引擎模式分词 使用自定义词典 关键词提取 停用词过滤 模块安装 pip install jieba jieb ...