Java項(xiàng)目添加慢SQL查詢工具的實(shí)踐指南
日期:2026-02-28
場(chǎng)景:校園項(xiàng)目訪問(wèn) Oracle 數(shù)據(jù)庫(kù),部分查詢慢,偶爾出現(xiàn) Hikari 連接池超時(shí),需要定位慢 SQL 并統(tǒng)計(jì)分析。
背景問(wèn)題
項(xiàng)目在高并發(fā)或大數(shù)據(jù)量查詢時(shí),經(jīng)常出現(xiàn):
Caused by: org.springframework.jdbc.CannotGetJdbcConnectionException:
Failed to obtain JDBC Connection; request timed out after 30000ms
初步排查發(fā)現(xiàn):
- Oracle 數(shù)據(jù)庫(kù)連接池配置較小,慢 SQL 導(dǎo)致連接占用過(guò)久
- 業(yè)務(wù)查詢中存在 LIKE、GROUP BY、分頁(yè)等操作,大數(shù)據(jù)量下執(zhí)行慢
目標(biāo):快速定位慢 SQL,統(tǒng)計(jì)出現(xiàn)次數(shù)和耗時(shí),便于優(yōu)化。
工具選型
選擇 P6Spy + Logback:
- P6Spy 攔截 JDBC 調(diào)用,透明記錄 SQL
- 可配置 慢 SQL 閾值(executionThreshold)
- 性能損耗極小
Spring Boot 配置示例
spring:
datasource:
driver-class-name: com.p6spy.engine.spy.P6SpyDriver
url: jdbc:p6spy:oracle:thin:@// ********** /orcl
username: *********
password: **********************
spy.properties:
appender=com.p6spy.engine.spy.appender.Slf4JLogger executionThreshold=1000 # 超過(guò)1秒才記錄 logMessageFormat=com.p6spy.engine.spy.appender.SingleLineFormat resultSetLoggable=false
logback.xml:
<appender name="SLOW_SQL_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_PATH}/slow-sql.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${LOG_PATH}/slow-sql-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<maxFileSize>50MB</maxFileSize>
<maxHistory>30</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %msg%n</pattern>
</encoder>
</appender>
<logger name="p6spy" level="INFO" additivity="false">
<appender-ref ref="SLOW_SQL_FILE"/>
</logger>
這樣就可以單獨(dú)輸出慢 SQL 到 slow-sql.log,不干擾 info.log、error.log。
分析思路
慢 SQL 日志樣例:
2026-02-28 17:03:13.617 1772269393616|1073|statement|connection 0|url jdbc:p6spy:oracle:thin:@//...|SELECT COUNT(*) ...
字段說(shuō)明:
| 字段 | 含義 |
|---|---|
| 1073 | SQL 執(zhí)行耗時(shí)(ms) |
| statement | SQL 類型 |
| connection 0 | JDBC 連接編號(hào) |
| SELECT ... | 實(shí)際執(zhí)行 SQL |
分析方法:
- 找出耗時(shí) > 閾值的 SQL
- 判斷 SQL 是否包含
LIKE、GROUP BY或分頁(yè) - 統(tǒng)計(jì)出現(xiàn)次數(shù)、最大耗時(shí)、平均耗時(shí)
- 結(jié)合連接池監(jiān)控判斷是否是連接占用導(dǎo)致超時(shí)
Java 分析工具實(shí)現(xiàn)
可以直接在 Java 環(huán)境運(yùn)行,無(wú)需 Python。
import java.io.*;
import java.util.*;
import java.util.regex.*;
public class SlowSqlAnalyzer {
public static void main(String[] args) throws IOException {
if (args.length < 1) {
System.out.println("Usage: java SlowSqlAnalyzer <slow-sql.log>");
return;
}
String logFile = args[0];
// 正則匹配 P6Spy SQL 日志行
Pattern sqlPattern = Pattern.compile("\\|\\d+\\|statement\\|.*?\\|(SELECT|INSERT|UPDATE|DELETE).*", Pattern.CASE_INSENSITIVE);
Map<String, SqlStats> statsMap = new HashMap<>();
try (BufferedReader reader = new BufferedReader(new FileReader(logFile))) {
String line;
while ((line = reader.readLine()) != null) {
String[] parts = line.split("\\|", 6);
if (parts.length < 6) continue;
int execTime;
try {
execTime = Integer.parseInt(parts[1]);
} catch (NumberFormatException e) {
continue;
}
String sqlText = parts[5].trim();
Matcher matcher = sqlPattern.matcher(line);
if (matcher.find()) {
// 用前200字符作為 key,避免重復(fù)太多
String key = sqlText.length() > 200 ? sqlText.substring(0, 200) : sqlText;
SqlStats s = statsMap.getOrDefault(key, new SqlStats(sqlText));
s.count++;
s.totalTime += execTime;
s.maxTime = Math.max(s.maxTime, execTime);
s.like = s.like || sqlText.toUpperCase().contains("LIKE");
s.groupBy = s.groupBy || sqlText.toUpperCase().contains("GROUP BY");
statsMap.put(key, s);
}
}
}
// 輸出統(tǒng)計(jì)結(jié)果
System.out.printf("%5s | %12s | %12s | %6s | %8s | %s%n",
"次數(shù)", "平均耗時(shí)(ms)", "最大耗時(shí)(ms)", "LIKE", "GROUP BY", "SQL示例前200字符");
System.out.println("--------------------------------------------------------------------------------------------------------");
statsMap.values().stream()
.sorted((a,b) -> Long.compare(b.totalTime, a.totalTime))
.forEach(s -> {
long avg = s.totalTime / s.count;
System.out.printf("%5d | %12d | %12d | %6b | %8b | %s%n",
s.count, avg, s.maxTime, s.like, s.groupBy, s.sqlSnippet);
});
}
static class SqlStats {
String sqlSnippet;
int count = 0;
long totalTime = 0;
long maxTime = 0;
boolean like = false;
boolean groupBy = false;
SqlStats(String sql) {
this.sqlSnippet = sql;
}
}
}
執(zhí)行方法:
javac SlowSqlAnalyzer.java java SlowSqlAnalyzer /data/javaApp/rest-tonp-realization/logs/slow-sql.log
輸出示例:
次數(shù) | 平均耗時(shí)(ms) | 最大耗時(shí)(ms) | LIKE | GROUP BY | SQL示例前200字符
--------------------------------------------------------------------------------------------------------
3 | 1234 | 1567 | true | true | SELECT ... FROM CERTIFICATION ...
2 | 1050 | 1200 | false| true | SELECT ... FROM PRODUCT_TYPE_NEW ...
收獲與優(yōu)化建議
可視化慢 SQL
- 統(tǒng)計(jì)出現(xiàn)次數(shù)、最大耗時(shí)、平均耗時(shí)
- 可快速定位熱點(diǎn) SQL
SQL 優(yōu)化方向
- 對(duì)頻繁
LIKE查詢字段加索引或改寫邏輯 - 對(duì)大數(shù)據(jù)量
GROUP BY/ 分頁(yè)查詢優(yōu)化(索引 + 分批) - 避免長(zhǎng)事務(wù)占用連接,結(jié)合 Hikari 連接池配置
整體收益
- 每條慢 SQL都可被記錄和統(tǒng)計(jì)
- 高并發(fā)環(huán)境下連接池超時(shí)問(wèn)題更容易排查
總結(jié):
通過(guò) P6Spy + Logback + Java 分析工具,可以快速定位慢 SQL 并統(tǒng)計(jì),為性能優(yōu)化提供數(shù)據(jù)支持。
到此這篇關(guān)于Java項(xiàng)目添加慢SQL查詢工具的實(shí)踐指南的文章就介紹到這了,更多相關(guān)Java添加慢SQL查詢內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
javax.xml.bind.JAXBContext操作XML的實(shí)現(xiàn)示例
JAXB是Java標(biāo)準(zhǔn)API,用于Java對(duì)象與XML的相互轉(zhuǎn)換,通過(guò)注解定義映射規(guī)則,本文就來(lái)詳細(xì)的介紹一下javax.xml.bind.JAXBContext操作XML的實(shí)現(xiàn),感興趣的可以了解一下2025-12-12
java使用jacob.jar將word轉(zhuǎn)pdf
這篇文章主要為大家詳細(xì)介紹了java利用jacob.jar將word轉(zhuǎn)pdf,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-12-12
Java 入門圖形用戶界面設(shè)計(jì)之事件處理上
圖形界面(簡(jiǎn)稱GUI)是指采用圖形方式顯示的計(jì)算機(jī)操作用戶界面。與早期計(jì)算機(jī)使用的命令行界面相比,圖形界面對(duì)于用戶來(lái)說(shuō)在視覺(jué)上更易于接受,本篇精講Java語(yǔ)言中關(guān)于圖形用戶界面的事件處理2022-02-02
SpringBoot整合EasyExcel實(shí)現(xiàn)復(fù)雜Excel表格的導(dǎo)入導(dǎo)出
這篇文章主要為大家詳細(xì)介紹了SpringBoot如何整合EasyExcel實(shí)現(xiàn)復(fù)雜Excel表格的導(dǎo)入導(dǎo)出功能,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以參考下2023-11-11
基于SpringBoot實(shí)現(xiàn)單賬號(hào)登錄控制功能
在很多業(yè)務(wù)場(chǎng)景中,我們需要控制單個(gè)賬號(hào)的登錄數(shù)量,本文介紹一種輕量級(jí)實(shí)現(xiàn)方案,靈活支持單賬號(hào)單登錄和多登錄兩種模式,有需要的小伙伴可以了解下2025-12-12

