目标记录每次请求内的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. 图像处理中Stride的理解

    一行有 11 个像素(Width = 11), 对一个 32 位(每个像素 4 字节)的图像, Stride = 11 * 4 = 44. 但还有个字节对齐的问题, 譬如: 一行有 11 个像素(Wi ...

  2. TensorFlow+restore读取模型

    # 注意和前一或二篇Lenet训练并验证的文章从`y_conv = tf.nn.softmax(fc2)`起的不同 # 部分函数请参照前后2篇文章 import tensorflow as tf im ...

  3. hihoCoder Challenge 1

    #1034 : 毁灭者问题 时间限制:10000ms 单点时限:1000ms 内存限制:256MB 描述 在 Warcraft III 之冰封王座中,毁灭者是不死族打三本后期时的一个魔法飞行单位. 毁 ...

  4. Promise thenable All In One

    Promise thenable All In One Promise thenable 是指一个函数或一个对象的里面定义了一个 then 方法 Promises/A+ https://promise ...

  5. GitHub Classroom

    GitHub Classroom GitHub Education https://classroom.github.com/classrooms https://classroom.github.c ...

  6. 一个最简单 node.js 命令行工具

    一个最简单 node.js 命令行工具 node.js cli $ node cli.js xyz # OR $ node cli xyz 接受参数 process.argv js "use ...

  7. web testing

    web testing cypress https://www.cypress.io/ https://github.com/cypress-io/cypress https://docs.cypre ...

  8. Nestjs 设置https

    文档 只是用https import * as fs from 'fs'; import { NestFactory } from '@nestjs/core'; import { AppModule ...

  9. django学习-23.admin管理后台的数据表数据的自定义展示

    目录结构 1.前言 2.自定义设置一张指定的数据表的列表展示内容 2.1.第一步:如果我们想让数据表[hello_person]里面的表字段值全部展示出来,需在应用[hello]里的[admin.py ...

  10. HttpDns 原理是什么

    本文转载自HttpDns 原理是什么 什么是 DNS DNS(Domain Name System,域名系统),DNS 服务用于在网络请求时,将域名转为 IP 地址.能够使用户更方便的访问互联网,而不 ...