java通過(guò)MySQL驅(qū)動(dòng)攔截器實(shí)現(xiàn)執(zhí)行sql耗時(shí)計(jì)算
背景
公司的一個(gè)需求,公司既有的鏈路追蹤日志組件要支持MySQL的sql執(zhí)行時(shí)間打印,要實(shí)現(xiàn)鏈路追蹤常用的手段就是實(shí)現(xiàn)第三方框架或工具提供的攔截器接口或者是過(guò)濾器接口,對(duì)于MySQL也不例外,實(shí)際上就是實(shí)現(xiàn)了MySQL驅(qū)動(dòng)的攔截器接口而已。
具體實(shí)現(xiàn)
MySQL的渠道有不同的版本,不同版本的攔截器接口是不同的,所以要針對(duì)你所使用的不同版本的MySQL驅(qū)動(dòng)去實(shí)現(xiàn)響應(yīng)的攔截器,接下來(lái)分別介紹下MySQL渠道5,6,8版本的實(shí)現(xiàn)方式。
MySQL5
這里以MySQL渠道5.1.18版本為例實(shí)現(xiàn),實(shí)現(xiàn)StatementInterceptorV2
接口,主要實(shí)現(xiàn)邏輯在preProcess
和postProcess
方法,這兩個(gè)方法是sql執(zhí)行前后要執(zhí)行的方法,我所使用的框架是logback,這里使用MDC來(lái)記錄sql執(zhí)行前的一個(gè)時(shí)間戳,代碼在postProcess
方法MDC.put("sql_exec_time", start);
,自己也可以使用ThreadLocal等來(lái)實(shí)現(xiàn),然后在postProcess
方法中使用MDC.get("sql_exec_time")
將記錄的sql執(zhí)行前的時(shí)間取出來(lái),最后再用當(dāng)前時(shí)間戳減去sql執(zhí)行前的時(shí)間,就算出了sql執(zhí)行的時(shí)間。
import static net.logstash.logback.marker.Markers.append; import com.mysql.jdbc.Connection; import com.mysql.jdbc.ResultSetInternalMethods; import com.mysql.jdbc.Statement; import com.mysql.jdbc.StatementInterceptorV2; import com.redick.util.LogUtil; import java.sql.SQLException; import java.util.Properties; import lombok.extern.slf4j.Slf4j; import org.slf4j.MDC; /** * @author Redick01 */ @Slf4j public class Mysql5StatementInterceptor implements StatementInterceptorV2 { @Override public void init(Connection connection, Properties properties) throws SQLException { } @Override public ResultSetInternalMethods preProcess(String s, Statement statement, Connection connection) throws SQLException { String start = String.valueOf(System.currentTimeMillis()); MDC.put("sql_exec_time", start); log.info(LogUtil.customizeMarker(LogUtil.kLOG_KEY_TRACE_TAG, "sql_exec_before"), "開始執(zhí)行sql"); return null; } @Override public boolean executeTopLevelOnly() { return false; } @Override public void destroy() { } @Override public ResultSetInternalMethods postProcess(String s, Statement statement, ResultSetInternalMethods resultSetInternalMethods, Connection connection, int i, boolean b, boolean b1, SQLException e) throws SQLException { long start = Long.parseLong(MDC.get("sql_exec_time")); long end = System.currentTimeMillis(); log.info(LogUtil.customizeMarker(LogUtil.kLOG_KEY_TRACE_TAG, "sql_exec_after") .and(append(LogUtil.kLOG_KEY_SQL_EXEC_DURATION, end - start)), "結(jié)束執(zhí)行sql"); return null; } }
MySQL6
MySQL6和MySQL5基本一樣,只是接口不是同一個(gè),直接放代碼
import static net.logstash.logback.marker.Markers.append; import com.mysql.cj.api.MysqlConnection; import com.mysql.cj.api.jdbc.Statement; import com.mysql.cj.api.jdbc.interceptors.StatementInterceptor; import com.mysql.cj.api.log.Log; import com.mysql.cj.api.mysqla.result.Resultset; import com.redick.util.LogUtil; import java.sql.SQLException; import java.util.Properties; import lombok.extern.slf4j.Slf4j; import org.slf4j.MDC; /** * @author Redick01 */ @Slf4j public class Mysql6StatementInterceptor implements StatementInterceptor { @Override public StatementInterceptor init(MysqlConnection mysqlConnection, Properties properties, Log log) { return null; } @Override public <T extends Resultset> T preProcess(String s, Statement statement) throws SQLException { String start = String.valueOf(System.currentTimeMillis()); MDC.put("sql_exec_time", start); log.info(LogUtil.customizeMarker(LogUtil.kLOG_KEY_TRACE_TAG, "sql_exec_before"), "開始執(zhí)行sql"); return null; } @Override public boolean executeTopLevelOnly() { return false; } @Override public void destroy() { } @Override public <T extends Resultset> T postProcess(String s, Statement statement, T t, int i, boolean b, boolean b1, Exception e) throws SQLException { long start = Long.parseLong(MDC.get("sql_exec_time")); long end = System.currentTimeMillis(); log.info(LogUtil.customizeMarker(LogUtil.kLOG_KEY_TRACE_TAG, "sql_exec_after") .and(append(LogUtil.kLOG_KEY_SQL_EXEC_DURATION, end - start)), "結(jié)束執(zhí)行sql"); return null; } }
MySQL8
MySQL8和MySQL5/6的攔截器接口又不一樣了,MySQL8的攔截器接口是com.mysql.cj.interceptors.QueryInterceptor
,統(tǒng)計(jì)sql執(zhí)行時(shí)間的方式還是一樣的,代碼如下:
import static net.logstash.logback.marker.Markers.append; import com.mysql.cj.MysqlConnection; import com.mysql.cj.Query; import com.mysql.cj.interceptors.QueryInterceptor; import com.mysql.cj.log.Log; import com.mysql.cj.protocol.Resultset; import com.mysql.cj.protocol.ServerSession; import com.redick.util.LogUtil; import java.util.Properties; import java.util.function.Supplier; import lombok.extern.slf4j.Slf4j; import org.slf4j.MDC; /** * @author Redick01 */ @Slf4j public class Mysql8QueryInterceptor implements QueryInterceptor { @Override public QueryInterceptor init(MysqlConnection mysqlConnection, Properties properties, Log log) { return null; } @Override public <T extends Resultset> T preProcess(Supplier<String> supplier, Query query) { String start = String.valueOf(System.currentTimeMillis()); MDC.put("sql_exec_time", start); log.info(LogUtil.customizeMarker(LogUtil.kLOG_KEY_TRACE_TAG, "sql_exec_before"), "開始執(zhí)行sql"); return null; } @Override public boolean executeTopLevelOnly() { return false; } @Override public void destroy() { } @Override public <T extends Resultset> T postProcess(Supplier<String> supplier, Query query, T t, ServerSession serverSession) { long start = Long.parseLong(MDC.get("sql_exec_time")); long end = System.currentTimeMillis(); log.info(LogUtil.customizeMarker(LogUtil.kLOG_KEY_TRACE_TAG, "sql_exec_after") .and(append(LogUtil.kLOG_KEY_SQL_EXEC_DURATION, end - start)), "結(jié)束執(zhí)行sql"); return null; } }
使用方法
MySQL5和6的使用方式一樣,在數(shù)據(jù)庫(kù)鏈接的url中增加如下statementInterceptors
參數(shù),例如:
url: jdbc:mysql://127.0.0.1:3316/log-helper?useUnicode=true&characterEncoding=UTF8&statementInterceptors=com.redick.support.mysql.Mysql5StatementInterceptor&serverTimezone=CST
MySQL8則是在url中增加queryInterceptors
參數(shù),例如:
url: jdbc:mysql://127.0.0.1:3316/log-helper?useUnicode=true&characterEncoding=UTF8&queryInterceptors=com.redick.support.mysql.Mysql8QueryInterceptor&serverTimezone=CST
測(cè)試結(jié)果
sql執(zhí)行前日志
{"@timestamp":"2023-02-28T17:16:29.234+08:00","@version":"0.0.1","message":"開始執(zhí)行sql","logger_name":"com.redick.support.mysql.Mysql5StatementInterceptor","thread_name":"http-nio-3321-exec-4","level":"INFO","level_value":20000,"traceId":"9ed930dc-4cc6-4719-bf33-9fcb618fd65b","spanId":"1","request_type":"getName","parentId":"0","trace_tag":"sql_exec_before"}
sql執(zhí)行后日志,sql_duration標(biāo)識(shí)執(zhí)行sql耗時(shí)3ms
{"@timestamp":"2023-02-28T17:16:29.237+08:00","@version":"0.0.1","message":"結(jié)束執(zhí)行sql","logger_name":"com.redick.support.mysql.Mysql5StatementInterceptor","thread_name":"http-nio-3321-exec-4","level":"INFO","level_value":20000,"traceId":"9ed930dc-4cc6-4719-bf33-9fcb618fd65b","spanId":"1","request_type":"getName","parentId":"0","trace_tag":"sql_exec_after","sql_duration":3}
具體實(shí)現(xiàn)代碼參考基于Spring AOP + logstash-logback-encoder日志鏈路追蹤工具LogHelper
到此這篇關(guān)于java通過(guò)MySQL驅(qū)動(dòng)攔截器實(shí)現(xiàn)執(zhí)行sql耗時(shí)計(jì)算的文章就介紹到這了,更多相關(guān)java執(zhí)行sql耗時(shí)計(jì)算內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
springboot實(shí)現(xiàn)微信掃碼登錄的項(xiàng)目實(shí)踐
微信掃碼功能是目前第三方登錄常見功能,前不久有個(gè)項(xiàng)目剛好用上,本文主要介紹了springboot實(shí)現(xiàn)微信掃碼登錄的項(xiàng)目實(shí)踐,具有一定的參考價(jià)值,感興趣的可以了解一下2023-10-10Jvm調(diào)優(yōu)和SpringBoot項(xiàng)目?jī)?yōu)化的詳細(xì)教程
這篇文章主要介紹了Jvm調(diào)優(yōu)和SpringBoot項(xiàng)目?jī)?yōu)化,本文通過(guò)圖文并茂的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-09-09Json字符串轉(zhuǎn)Java對(duì)象和List代碼實(shí)例
這篇文章主要介紹了Json字符串轉(zhuǎn)Java對(duì)象和List代碼實(shí)例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-06-06Java Metrics系統(tǒng)性能監(jiān)控工具的使用詳解
Metrics是一個(gè)Java庫(kù),可以對(duì)系統(tǒng)進(jìn)行監(jiān)控,統(tǒng)計(jì)一些系統(tǒng)的性能指標(biāo)。本文就來(lái)和大家詳細(xì)聊聊這個(gè)工具的具體使用,希望對(duì)大家有所幫助2022-11-11Java8 Stream對(duì)兩個(gè) List 遍歷匹配數(shù)據(jù)的優(yōu)化處理操作
這篇文章主要介紹了Java8 Stream對(duì)兩個(gè) List 遍歷匹配數(shù)據(jù)的優(yōu)化處理操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-08-08Java Netty實(shí)現(xiàn)心跳機(jī)制過(guò)程解析
這篇文章主要介紹了Java Netty實(shí)現(xiàn)心跳機(jī)制過(guò)程解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-03-03關(guān)于Java中try finally return語(yǔ)句的執(zhí)行順序淺析
這篇文章主要介紹了關(guān)于Java中try finally return語(yǔ)句的執(zhí)行順序淺析,需要的朋友可以參考下2017-08-08