如何利用日志链路追踪程序执行的慢SQL?

导读:查看SQL的执行效率,不难想到使用explain分析慢查询,但是前提是你需要非常了解业务背景。否则很难精准定位到。

系统都是逐渐演进的,一个系统在运行中必须是根据场景逐渐地提高优化性能。高并发就是对资源的节约的考验,这种考验除了更换优秀和先进的技术,优化架构,还在于从小处出发,对尽可能节约的资源进行节约。

而在一个系统的数据访问中,系统的瓶颈往往是来自于数据库,因此我们要尽可能减少对数据库的访问!

一、背景

有没有遇到这种情况,领导突然安排一件事情:这几个接口压测指标太低需要针对性优化一下

当然理想的情况下你对业务场景非常熟悉,可以大概定位问题来分析业务精准评估哪些SQL会有性能瓶颈。

然后开始百度:如何提高SQL执行效率?

通过 explain、show profile 和 trace 等诊断工具来分析慢查询。

但是大多数情况下业务线过长,不可能一个人完成。涉及到各种策略模式、监听动作。想直接定位到点还是需要输出请求发起后所触发的执行的SQL以及执行效率。这里效率单单指代SQL执行的时间。

目标明确后开始整活吧。

二、添加JDBC追踪

继续前一篇文章的话题:如何利用好日志链路追踪做性能分析?

SQL执行时间公式

要想处理此类问题首先的分析,SQL执行时间计算如何来划分?SQL的语句执行过程大致如下图所示。

如果想统计SQL执行时间。所以对于程序而言可以得到粗略公式

  • SQL执行时间=提取数据之后时间-语法解析开始时间

添加增加JDBC追踪

阅读过Hibernate或者MyBatis等持久化框架的应该比较了解Statement位于java.sql基础包下

Statement提供了用于执行静态 SQL 语句并返回它产生的结果的对象。默认情况下,每个Statement对象只能同时打开一个ResultSet对象。

因此,如果一个ResultSet对象的读取与另一个的读取交错,则每个对象都必须由不同的Statement对象生成。如果存在打开的对象,则Statement接口中的所有执行方法都会隐式关闭该Statement的当前ResultSet对象。

继续查看源码可以发现在Statement提供了用于执行方法后,PreparedStatement预编译 SQL语句的对象。SQL 语句被预编译并存储在PreparedStatement对象中。然后可以使用此对象多次有效地执行此语句。

为了验证这个思路,可以借鉴其他定制化数据库驱动。

定义StatementWraper实现Statement提供了用于执行静态 SQL 语句并返回它产生的结果的对象。

记录日志详情

  • public class StatementWraper implements Statement {
  • private Statement raw;
  • public StatementWraper(Statement raw) {
  • super();
  • this.raw = raw;
  • }
  • /**
  • * 记录日志
  • */
  • protected void doLog(String opt, String sql, long startTime) {
  • if (sql != null) {
  • sql = sql.replaceAll("\\\\s+", " ");
  • }
  • TraceUtil.log(StringHelper.join(
  • "dt:", System.currentTimeMillis() - startTime,
  • ",TRACE-JDBC:", opt,
  • ",SQL:",sql));
  • }
  • // ========== 记录日志 ==========
  • @Override
  • public ResultSet executeQuery(String sql) throws SQLException {
  • long startTime = System.currentTimeMillis();
  • try {
  • return raw.executeQuery(sql);
  • } finally {
  • doLog("executeQuery", sql, startTime);
  • }
  • }
  • ....

这样就有了文章开头的程序执行过程中锁触发的SQL执行耗时情况。

同理定义PreparedStatementWraper实现PreparedStatement

  • public class PreparedStatementWraper extends StatementWraper
  • implements PreparedStatement {
  • private PreparedStatement raw;
  • private String sql;
  • public PreparedStatementWraper(PreparedStatement raw, String sql) {
  • super(raw);
  • this.raw = raw;
  • this.sql = sql;
  • }
  • // ========== 记录日志 ==========
  • @Override
  • public ResultSet executeQuery() throws SQLException {
  • long startTime = System.currentTimeMillis();
  • try {
  • return raw.executeQuery();
  • } finally {
  • doLog("executeQuery", sql, startTime);
  • }
  • }
  • ....

最后日志输出使用logback组件进行日志采集

对这类问题先前有做介绍

本站文章资源均来源自网络,除非特别声明,否则均不代表站方观点,并仅供查阅,不作为任何参考依据!
如有侵权请及时跟我们联系,本站将及时删除!
如遇版权问题,请查看 本站版权声明
THE END
分享
二维码
海报
<<上一篇
下一篇>>