SpringBoot實(shí)現(xiàn)對(duì)數(shù)據(jù)庫(kù)慢查詢監(jiān)控的方案小結(jié)
在企業(yè)級(jí)應(yīng)用開(kāi)發(fā)中,數(shù)據(jù)庫(kù)性能往往是系統(tǒng)整體性能的關(guān)鍵瓶頸。慢查詢不僅會(huì)影響用戶體驗(yàn),還可能導(dǎo)致連接池耗盡,進(jìn)而引發(fā)系統(tǒng)雪崩。
因此,對(duì)數(shù)據(jù)庫(kù)慢查詢進(jìn)行有效監(jiān)控和及時(shí)優(yōu)化,是保障系統(tǒng)穩(wěn)定運(yùn)行的重要環(huán)節(jié)。
本文將介紹6種在SpringBoot應(yīng)用中實(shí)現(xiàn)慢查詢監(jiān)控的方案。
一、數(shù)據(jù)庫(kù)原生慢查詢?nèi)罩?/h2>
原理概述
幾乎所有主流關(guān)系型數(shù)據(jù)庫(kù)都提供了內(nèi)置的慢查詢?nèi)罩竟δ?,通過(guò)設(shè)置閾值,將執(zhí)行時(shí)間超過(guò)閾值的SQL記錄到專門(mén)的日志文件中。
實(shí)現(xiàn)方式
以MySQL為例:
1. 配置慢查詢?nèi)罩?/strong>
修改MySQL配置文件(my.cnf):
# 開(kāi)啟慢查詢?nèi)罩? slow_query_log = 1 # 慢查詢?nèi)罩疚募恢? slow_query_log_file = /var/log/mysql/mysql-slow.log # 設(shè)置慢查詢閾值(秒) long_query_time = 1 # 記錄沒(méi)有使用索引的查詢 log_queries_not_using_indexes = 1
2. 在SpringBoot中查看慢查詢?nèi)罩?/strong>
@Repository
public class SlowQueryAnalyzer {
@Autowired
private JdbcTemplate jdbcTemplate;
public List<Map<String, Object>> getSlowQueries() {
String sql = "SELECT * FROM mysql.slow_log ORDER BY start_time DESC LIMIT 100";
return jdbcTemplate.queryForList(sql);
}
public void analyzeSlowQueries() {
String sql = "SELECT COUNT(*) as count, db, sql_text, AVG(query_time) as avg_time " +
"FROM mysql.slow_log " +
"GROUP BY db, sql_text " +
"ORDER BY avg_time DESC " +
"LIMIT 10";
List<Map<String, Object>> result = jdbcTemplate.queryForList(sql);
// 處理結(jié)果...
}
}優(yōu)缺點(diǎn)分析
優(yōu)點(diǎn):
- 零代碼侵入,無(wú)需修改應(yīng)用代碼
- 數(shù)據(jù)庫(kù)原生支持,準(zhǔn)確性高
- 可捕獲所有慢查詢,包括非應(yīng)用發(fā)起的查詢
缺點(diǎn):
- 需要數(shù)據(jù)庫(kù)管理員權(quán)限配置
- 增加數(shù)據(jù)庫(kù)I/O負(fù)擔(dān),生產(chǎn)環(huán)境需謹(jǐn)慎使用
- 日志分析需要額外工具支持
- 無(wú)法與應(yīng)用上下文關(guān)聯(lián)(如調(diào)用方法、請(qǐng)求URL等)
適用場(chǎng)景
• 開(kāi)發(fā)和測(cè)試環(huán)境的問(wèn)題排查
• 對(duì)數(shù)據(jù)庫(kù)有完全控制權(quán)的場(chǎng)景
• 需要捕獲所有數(shù)據(jù)庫(kù)操作的場(chǎng)景
• 基礎(chǔ)設(shè)施層面的監(jiān)控需求
二、基于AOP的慢查詢監(jiān)控
原理概述
利用Spring AOP機(jī)制,在Repository方法執(zhí)行前后添加切面,計(jì)算執(zhí)行時(shí)間并記錄超過(guò)閾值的方法調(diào)用。
實(shí)現(xiàn)方式
1. 添加AOP依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>2. 創(chuàng)建慢查詢監(jiān)控切面
@Aspect
@Component
@Slf4j
public class SlowQueryAspect {
@Value("${slow.query.threshold:500}")
private long slowQueryThreshold; // 默認(rèn)閾值500毫秒
@Around("execution(* com.example.repository.*.*(..))")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
String methodName = joinPoint.getSignature().toShortString();
long startTime = System.currentTimeMillis();
Object result = joinPoint.proceed();
long executionTime = System.currentTimeMillis() - startTime;
if (executionTime > slowQueryThreshold) {
String args = Arrays.toString(joinPoint.getArgs());
log.warn("Slow Query detected: {} with args {}, execution time: {} ms",
methodName, args, executionTime);
// 可以將慢查詢信息保存到數(shù)據(jù)庫(kù)或發(fā)送告警
saveSlowQueryInfo(methodName, args, executionTime);
}
return result;
}
private void saveSlowQueryInfo(String methodName, String args, long executionTime) {
// 保存慢查詢信息到數(shù)據(jù)庫(kù)或發(fā)送到監(jiān)控系統(tǒng)
}
}3. 創(chuàng)建慢查詢事件監(jiān)聽(tīng)器(可選)
@Component
@Slf4j
public class SlowQueryEventListener {
@Autowired
private ApplicationEventPublisher eventPublisher;
public void onSlowQuery(String methodName, String args, long executionTime) {
SlowQueryEvent event = new SlowQueryEvent(this, methodName, args, executionTime);
eventPublisher.publishEvent(event);
}
@EventListener
public void handleSlowQueryEvent(SlowQueryEvent event) {
// 處理慢查詢事件,如發(fā)送告警郵件、存儲(chǔ)到時(shí)序數(shù)據(jù)庫(kù)等
log.warn("Handling slow query event: {}", event);
}
}
@Getter
public class SlowQueryEvent extends ApplicationEvent {
private final String methodName;
private final String args;
private final long executionTime;
public SlowQueryEvent(Object source, String methodName, String args, long executionTime) {
super(source);
this.methodName = methodName;
this.args = args;
this.executionTime = executionTime;
}
}優(yōu)缺點(diǎn)分析
優(yōu)點(diǎn):
- 實(shí)現(xiàn)簡(jiǎn)單,代碼侵入性低
- 可以捕獲完整的方法調(diào)用上下文
- 靈活可定制,可以根據(jù)需求調(diào)整監(jiān)控策略
- 可以與應(yīng)用現(xiàn)有的監(jiān)控系統(tǒng)集成
缺點(diǎn):
- 只能監(jiān)控應(yīng)用代碼中的查詢,無(wú)法監(jiān)控原生SQL
- 性能開(kāi)銷較大,特別是在高并發(fā)場(chǎng)景
- 可能出現(xiàn)AOP失效的場(chǎng)景(如內(nèi)部方法調(diào)用)
- 無(wú)法獲取到實(shí)際執(zhí)行的SQL語(yǔ)句
適用場(chǎng)景
• 小型應(yīng)用或并發(fā)量不大的系統(tǒng)
• 需要監(jiān)控特定Repository方法性能的場(chǎng)景
• 開(kāi)發(fā)或測(cè)試環(huán)境的性能調(diào)優(yōu)
• 已經(jīng)廣泛使用Spring AOP的項(xiàng)目
三、Spring Boot Actuator + Micrometer
原理概述
利用Spring Boot Actuator和Micrometer提供的指標(biāo)收集功能,監(jiān)控?cái)?shù)據(jù)庫(kù)操作性能,并將數(shù)據(jù)導(dǎo)出到監(jiān)控系統(tǒng)。
實(shí)現(xiàn)方式
1. 添加依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>2. 配置Actuator和數(shù)據(jù)源監(jiān)控
在application.properties中添加:
# 開(kāi)啟所有Actuator端點(diǎn) management.endpoints.web.exposure.include=* # 啟用數(shù)據(jù)庫(kù)指標(biāo)收集 management.metrics.enable.jdbc=true # 配置Prometheus端點(diǎn) management.metrics.export.prometheus.enabled=true
3. 自定義數(shù)據(jù)源代理,添加指標(biāo)收集
@Configuration
public class DataSourceProxyConfig {
@Bean
@Primary
public DataSource dataSource(DataSource originalDataSource, MeterRegistry meterRegistry) {
return ProxyDataSourceBuilder
.create(originalDataSource)
.name("metrics-ds")
.listener(new QueryExecutionListener() {
@Override
public void beforeQuery(ExecutionInfo execInfo, List<QueryInfo> queryInfoList) {
// 查詢執(zhí)行前的操作
}
@Override
public void afterQuery(ExecutionInfo execInfo, List<QueryInfo> queryInfoList) {
long elapsedTime = execInfo.getElapsedTime();
// 記錄查詢時(shí)間指標(biāo)
Timer.builder("datasource.query.time")
.tag("success", String.valueOf(execInfo.isSuccess()))
.tag("query", getSafeQueryName(queryInfoList))
.register(meterRegistry)
.record(elapsedTime, TimeUnit.MILLISECONDS);
// 檢測(cè)慢查詢并記錄
if (elapsedTime > 500) { // 500ms閾值
Counter.builder("datasource.slow.queries")
.tag("query", getSafeQueryName(queryInfoList))
.register(meterRegistry)
.increment();
// 可以記錄慢查詢?nèi)罩?
logSlowQuery(execInfo, queryInfoList);
}
}
private String getSafeQueryName(List<QueryInfo> queryInfoList) {
if (queryInfoList.isEmpty()) {
return "unknown";
}
String sql = queryInfoList.get(0).getQuery();
// 簡(jiǎn)化SQL以避免過(guò)多的唯一標(biāo)簽
return DigestUtils.md5DigestAsHex(sql.getBytes()).substring(0, 8);
}
private void logSlowQuery(ExecutionInfo execInfo, List<QueryInfo> queryInfoList) {
// 記錄慢查詢?cè)斍?
}
})
.build();
}
}ProxyDataSourceBuilder 來(lái)自開(kāi)源庫(kù)datasource-proxy,datasource-proxy可以用于 JDBC 數(shù)據(jù)源的代理,可以用來(lái)攔截和監(jiān)控 SQL 查詢執(zhí)行,實(shí)現(xiàn) SQL 日志記錄、性能監(jiān)控、查詢統(tǒng)計(jì)等功能。
<dependency>
<groupId>net.ttddyy</groupId>
<artifactId>datasource-proxy</artifactId>
<version>1.9</version>
</dependency>4. 創(chuàng)建自定義端點(diǎn)查看慢查詢
@Component
@Endpoint(id = "slowqueries")
public class SlowQueryEndpoint {
@Autowired
private MeterRegistry meterRegistry;
@ReadOperation
public Map<String, Object> slowQueries() {
Map<String, Object> result = new HashMap<>();
// 獲取慢查詢計(jì)數(shù)器
List<Meter> meters = meterRegistry.getMeters().stream()
.filter(m -> m.getId().getName().equals("datasource.slow.queries"))
.collect(Collectors.toList());
Map<String, Double> queryCounts = new HashMap<>();
for (Meter meter : meters) {
String query = meter.getId().getTag("query");
double count = ((Counter) meter).count();
queryCounts.put(query, count);
}
result.put("counts", queryCounts);
// 獲取慢查詢時(shí)間分布
List<Meter> timers = meterRegistry.getMeters().stream()
.filter(m -> m.getId().getName().equals("datasource.query.time"))
.collect(Collectors.toList());
Map<String, Map<String, Object>> queryTimes = new HashMap<>();
for (Meter meter : timers) {
String query = meter.getId().getTag("query");
Timer timer = (Timer) meter;
Map<String, Object> stats = new HashMap<>();
stats.put("count", timer.count());
stats.put("max", timer.max(TimeUnit.MILLISECONDS));
stats.put("mean", timer.mean(TimeUnit.MILLISECONDS));
stats.put("percentile95", timer.takeSnapshot().percentileValues()[0].value(TimeUnit.MILLISECONDS));
queryTimes.put(query, stats);
}
result.put("times", queryTimes);
return result;
}
}優(yōu)缺點(diǎn)分析
優(yōu)點(diǎn):
- 與Spring Boot生態(tài)緊密集成
- 支持多種監(jiān)控系統(tǒng),如Prometheus、Grafana等
- 提供豐富的指標(biāo)和可視化能力
- 運(yùn)行時(shí)監(jiān)控,影響生產(chǎn)代碼較小
缺點(diǎn):
- 配置相對(duì)復(fù)雜
- 資源消耗較大,特別是在大量指標(biāo)收集的情況下
- 需要額外的監(jiān)控系統(tǒng)支持
- 學(xué)習(xí)成本較高
適用場(chǎng)景
• 中大型微服務(wù)架構(gòu)
• 已經(jīng)使用Prometheus + Grafana等監(jiān)控系統(tǒng)的團(tuán)隊(duì)
• 需要全面監(jiān)控系統(tǒng)性能的場(chǎng)景
• 對(duì)指標(biāo)和可視化有較高要求的項(xiàng)目
四、使用P6Spy進(jìn)行SQL性能監(jiān)控
原理概述
P6Spy是一個(gè)開(kāi)源的JDBC代理框架,能夠攔截JDBC操作并記錄SQL語(yǔ)句的執(zhí)行情況,包括執(zhí)行時(shí)間、參數(shù)等信息。
實(shí)現(xiàn)方式
1. 添加依賴
<dependency>
<groupId>p6spy</groupId>
<artifactId>p6spy</artifactId>
<version>3.9.1</version>
</dependency>2. 配置數(shù)據(jù)源
修改數(shù)據(jù)源配置,將驅(qū)動(dòng)類替換為P6Spy的代理驅(qū)動(dòng):
# 原始配置 #spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver #spring.datasource.url=jdbc:mysql://localhost:3306/test # P6Spy配置 spring.datasource.driver-class-name=com.p6spy.engine.spy.P6SpyDriver spring.datasource.url=jdbc:p6spy:mysql://localhost:3306/test
3. 創(chuàng)建P6Spy配置文件
在resources目錄下創(chuàng)建spy.properties文件:
# 指定日志輸出模塊 appender=com.p6spy.engine.spy.appender.Slf4JLogger # 日志格式 logMessageFormat=com.example.config.CustomP6SpyLogFormat # 是否開(kāi)啟慢SQL記錄 outagedetection=true # 慢SQL閾值(毫秒) outagedetectioninterval=2000 # 設(shè)置 p6spy driver 代理 deregisterdrivers=true # 日期格式 dateformat=yyyy-MM-dd HH:mm:ss # 實(shí)際驅(qū)動(dòng) driverlist=com.mysql.cj.jdbc.Driver
4. 自定義日志格式化器
package com.example.config;
import com.p6spy.engine.spy.appender.MessageFormattingStrategy;
import org.springframework.util.StringUtils;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
public class CustomP6SpyLogFormat implements MessageFormattingStrategy {
private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
@Override
public String formatMessage(int connectionId, String now, long elapsed, String category,
String prepared, String sql, String url) {
return StringUtils.hasText(sql) ?
LocalDateTime.now().format(formatter) +
" | " + elapsed + "ms | " + category +
" | connection " + connectionId +
" | " + sql : "";
}
}5. 創(chuàng)建P6Spy慢查詢監(jiān)聽(tīng)器
package com.example.config;
import com.p6spy.engine.common.ConnectionInformation;
import com.p6spy.engine.event.JdbcEventListener;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.sql.SQLException;
import java.util.concurrent.TimeUnit;
@Slf4j
@Component
public class SlowQueryListener extends JdbcEventListener {
@Value("${sql.slow.threshold:500}")
private long slowThreshold; // 默認(rèn)500毫秒
@Override
public void onAfterAnyExecute(ConnectionInformation connectionInformation, long timeElapsedNanos, SQLException e) {
long timeElapsedMillis = TimeUnit.NANOSECONDS.toMillis(timeElapsedNanos);
if (timeElapsedMillis > slowThreshold) {
String query = connectionInformation.getSqlWithValues();
log.warn("Slow SQL detected: {} ms, SQL: {}", timeElapsedMillis, query);
// 可以記錄到數(shù)據(jù)庫(kù)或發(fā)送告警
saveSlowQuery(query, timeElapsedMillis);
}
}
private void saveSlowQuery(String query, long timeElapsed) {
// 保存慢查詢記錄到數(shù)據(jù)庫(kù)或告警系統(tǒng)
}
}優(yōu)缺點(diǎn)分析
優(yōu)點(diǎn):
- 能夠獲取完整的SQL語(yǔ)句和參數(shù)值
- 配置簡(jiǎn)單,幾乎零代碼侵入
- 可以監(jiān)控所有JDBC操作,包括非ORM框架的查詢
- 提供豐富的自定義選項(xiàng)
缺點(diǎn):
- 對(duì)性能有一定影響,不建議在高負(fù)載生產(chǎn)環(huán)境長(zhǎng)期開(kāi)啟
- 日志量較大,需要合理配置
- 可能與某些特定數(shù)據(jù)庫(kù)驅(qū)動(dòng)不兼容
- 不提供內(nèi)置的圖形化監(jiān)控界面
適用場(chǎng)景
• 開(kāi)發(fā)和測(cè)試環(huán)境的SQL調(diào)優(yōu)
• 需要詳細(xì)了解SQL執(zhí)行情況的場(chǎng)景
• 排查特定SQL問(wèn)題的臨時(shí)監(jiān)控
• 對(duì)SQL執(zhí)行參數(shù)有監(jiān)控需求的場(chǎng)景
五、基于APM工具的慢查詢監(jiān)控
原理概述
應(yīng)用性能監(jiān)控(APM)工具如SkyWalking、Pinpoint、Elastic APM等通過(guò)Java Agent技術(shù)在字節(jié)碼級(jí)別插樁,實(shí)現(xiàn)對(duì)數(shù)據(jù)庫(kù)操作的全方位監(jiān)控。
實(shí)現(xiàn)方式
以SkyWalking為例:
1. 下載SkyWalking Agent
從SkyWalking官網(wǎng)下載Agent包。
2. 配置Java Agent
在啟動(dòng)命令中添加:
java -javaagent:/path/to/skywalking-agent.jar -Dskywalking.agent.service_name=your-service-name -jar your-application.jar
或在Spring Boot應(yīng)用中通過(guò)環(huán)境變量配置:
# application.yml
spring:
application:
name: your-service-name3. 配置SkyWalking Agent
修改agent.config文件:
# 設(shè)置后端服務(wù)地址 collector.backend_service=localhost:11800 # 啟用SQL跟蹤 plugin.jdbc.trace_sql=true # 設(shè)置慢SQL閾值 plugin.jdbc.slow_sql_threshold=1000
4. 集成SkyWalking API(可選)
<dependency>
<groupId>org.apache.skywalking</groupId>
<artifactId>apm-toolkit-trace</artifactId>
<version>8.7.0</version>
</dependency>import org.apache.skywalking.apm.toolkit.trace.ActiveSpan;
import org.apache.skywalking.apm.toolkit.trace.TraceContext;
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
public User findUserById(Long id) {
// 添加自定義Tag
ActiveSpan.tag("userId", id.toString());
// 獲取traceId,可用于日志關(guān)聯(lián)
String traceId = TraceContext.traceId();
log.info("Processing user query with traceId: {}", traceId);
return userRepository.findById(id).orElse(null);
}
}優(yōu)缺點(diǎn)分析
優(yōu)點(diǎn):
- 全方位監(jiān)控,包括HTTP請(qǐng)求、數(shù)據(jù)庫(kù)操作、遠(yuǎn)程調(diào)用等
- 分布式追蹤能力,可以跟蹤完整調(diào)用鏈
- 零代碼侵入(基礎(chǔ)功能)
- 提供豐富的可視化界面和告警功能
- 支持多種存儲(chǔ)后端(ElasticSearch、MySQL等)
缺點(diǎn):
- 部署復(fù)雜,需要額外維護(hù)監(jiān)控服務(wù)器
- 資源消耗較大,增加應(yīng)用內(nèi)存占用
- 學(xué)習(xí)成本較高
- 可能與某些安全策略沖突(如禁止Java Agent)
適用場(chǎng)景
• 中大型分布式系統(tǒng)
• 微服務(wù)架構(gòu)應(yīng)用
• 需要完整分布式追蹤的場(chǎng)景
• 生產(chǎn)環(huán)境監(jiān)控
• 需要同時(shí)監(jiān)控多種性能指標(biāo)的場(chǎng)景
六、基于Druid連接池的慢查詢監(jiān)控
原理概述
阿里巴巴開(kāi)源的Druid連接池內(nèi)置了強(qiáng)大的監(jiān)控功能,包括慢查詢統(tǒng)計(jì)、SQL防火墻等。
實(shí)現(xiàn)方式
1. 添加依賴
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.8</version>
</dependency>2. 配置Druid
在application.properties中添加:
# 數(shù)據(jù)源類型 spring.datasource.type=com.alibaba.druid.pool.DruidDataSource spring.datasource.druid.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.druid.url=jdbc:mysql://localhost:3306/test spring.datasource.druid.username=root spring.datasource.druid.password=password # 連接池配置 spring.datasource.druid.initial-size=5 spring.datasource.druid.min-idle=5 spring.datasource.druid.max-active=20 spring.datasource.druid.max-wait=60000 # 慢SQL監(jiān)控配置 spring.datasource.druid.filter.stat.enabled=true spring.datasource.druid.filter.stat.log-slow-sql=true spring.datasource.druid.filter.stat.slow-sql-millis=1000 spring.datasource.druid.filter.stat.merge-sql=true # 開(kāi)啟監(jiān)控頁(yè)面 spring.datasource.druid.stat-view-servlet.enabled=true spring.datasource.druid.stat-view-servlet.url-pattern=/druid/* spring.datasource.druid.stat-view-servlet.login-username=admin spring.datasource.druid.stat-view-servlet.login-password=admin spring.datasource.druid.stat-view-servlet.allow=127.0.0.1 spring.datasource.druid.stat-view-servlet.deny= # 開(kāi)啟Web應(yīng)用監(jiān)控 spring.datasource.druid.web-stat-filter.enabled=true spring.datasource.druid.web-stat-filter.url-pattern=/* spring.datasource.druid.web-stat-filter.exclusions=*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/* # 開(kāi)啟Spring監(jiān)控 spring.datasource.druid.aop-patterns=com.example.service.*,com.example.repository.*
3. 配置Druid監(jiān)控(Java Config方式)
@Configuration
public class DruidConfig {
@Bean
public ServletRegistrationBean<StatViewServlet> druidStatViewServlet() {
ServletRegistrationBean<StatViewServlet> bean = new ServletRegistrationBean<>(new StatViewServlet(), "/druid/*");
Map<String, String> initParams = new HashMap<>();
initParams.put("loginUsername", "admin");
initParams.put("loginPassword", "admin");
initParams.put("allow", "127.0.0.1");
bean.setInitParameters(initParams);
return bean;
}
@Bean
public FilterRegistrationBean<WebStatFilter> druidWebStatFilter() {
FilterRegistrationBean<WebStatFilter> bean = new FilterRegistrationBean<>(new WebStatFilter());
Map<String, String> initParams = new HashMap<>();
initParams.put("exclusions", "*.js,*.css,/druid/*");
bean.setInitParameters(initParams);
bean.setUrlPatterns(Collections.singletonList("/*"));
return bean;
}
@Bean
@ConfigurationProperties("spring.datasource.druid.filter.stat")
public StatFilter statFilter() {
StatFilter filter = new StatFilter();
filter.setSlowSqlMillis(1000);
filter.setLogSlowSql(true);
filter.setMergeSql(true);
return filter;
}
@Bean
public DruidStatInterceptor druidStatInterceptor() {
return new DruidStatInterceptor();
}
@Bean
public BeanNameAutoProxyCreator druidStatProxyCreator() {
BeanNameAutoProxyCreator creator = new BeanNameAutoProxyCreator();
creator.setProxyTargetClass(true);
creator.setBeanNames("*Service", "*ServiceImpl", "*Repository");
creator.setInterceptorNames("druidStatInterceptor");
return creator;
}
}4. 自定義慢查詢監(jiān)聽(tīng)器(可選)
@Component
public class DruidSlowSqlListener implements ApplicationListener<ContextRefreshedEvent> {
@Autowired
private DruidDataSource dataSource;
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
dataSource.setConnectionProperties("druid.stat.slowSqlMillis=1000");
StatFilter statFilter = new StatFilter();
statFilter.setLogSlowSql(true);
statFilter.setSlowSqlMillis(1000);
statFilter.setMergeSql(true);
statFilter.setSlowSqlLoggerName("SLOW_SQL_LOGGER");
dataSource.getProxyFilters().add(statFilter);
}
}5. 自定義慢查詢Controller(可選)
@RestController
@RequestMapping("/api/monitor")
public class DruidMonitorController {
@Autowired
private DruidDataSource dataSource;
@GetMapping("/slow-sql")
public List<Map<String, Object>> getSlowSql() {
List<Map<String, Object>> result = new ArrayList<>();
try {
JdbcStatManager statManager = JdbcStatManager.getInstance();
for (Object item : statManager.getDataSourceList().values()) {
JdbcDataSourceStat dataSourceStat = (JdbcDataSourceStat) item;
Map<String, JdbcSqlStat> sqlStatMap = dataSourceStat.getSqlStatMap();
for (Map.Entry<String, JdbcSqlStat> entry : sqlStatMap.entrySet()) {
JdbcSqlStat sqlStat = entry.getValue();
if (sqlStat.getExecuteMillisMax() > 1000) {
Map<String, Object> slowSql = new HashMap<>();
slowSql.put("sql", sqlStat.getSql());
slowSql.put("executionCount", sqlStat.getExecuteCount());
slowSql.put("maxTime", sqlStat.getExecuteMillisMax());
slowSql.put("avgTime", sqlStat.getExecuteMillisTotal() / sqlStat.getExecuteCount());
result.add(slowSql);
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
}優(yōu)缺點(diǎn)分析
優(yōu)點(diǎn):
- 集成度高,開(kāi)箱即用
- 自帶可視化監(jiān)控界面
- 功能全面,除慢查詢外還有連接池監(jiān)控、SQL防火墻等
缺點(diǎn):
- 僅適用于使用Druid連接池的場(chǎng)景
- 與其他監(jiān)控系統(tǒng)集成需要額外開(kāi)發(fā)
- 默認(rèn)監(jiān)控頁(yè)面功能固定,不易擴(kuò)展
- 安全配置較為重要,否則可能泄露敏感信息
適用場(chǎng)景
• 對(duì)數(shù)據(jù)庫(kù)性能有全面監(jiān)控需求的場(chǎng)景
• 需要開(kāi)箱即用監(jiān)控功能的項(xiàng)目
• 小型到中型規(guī)模的應(yīng)用
• 對(duì)監(jiān)控?cái)?shù)據(jù)安全性有要求的場(chǎng)景
七、方案對(duì)比
| 方案 | 實(shí)現(xiàn)復(fù)雜度 | 代碼侵入性 | 性能影響 | 監(jiān)控全面性 | 可視化能力 |
| 數(shù)據(jù)庫(kù)原生慢查詢?nèi)罩?/td> | 低 | 無(wú) | 中 | 高 | 低 |
| 基于AOP的監(jiān)控 | 低 | 低 | 中 | 中 | 低 |
| Spring Boot Actuator + Micrometer | 中 | 低 | 中 | 高 | 高(需外部系統(tǒng)) |
| P6Spy | 低 | 低 | 中高 | 高 | 低 |
| APM工具(SkyWalking等) | 高 | 低 | 中高 | 極高 | 高 |
| Druid連接池 | 低 | 低 | 低 | 高 | 中 |
總結(jié)
慢查詢監(jiān)控是數(shù)據(jù)庫(kù)性能優(yōu)化的重要環(huán)節(jié),選擇合適的監(jiān)控方案對(duì)于提升應(yīng)用性能至關(guān)重要。
在實(shí)際應(yīng)用中,可以根據(jù)項(xiàng)目規(guī)模、技術(shù)棧和團(tuán)隊(duì)能力選擇合適的方案,也可以組合使用多種方案,實(shí)現(xiàn)更全面的監(jiān)控覆蓋。隨著應(yīng)用的發(fā)展,監(jiān)控策略也應(yīng)該不斷演進(jìn)和優(yōu)化,以適應(yīng)不斷變化的性能需求。
到此這篇關(guān)于SpringBoot實(shí)現(xiàn)對(duì)數(shù)據(jù)庫(kù)慢查詢監(jiān)控的方案小結(jié)的文章就介紹到這了,更多相關(guān)SpringBoot數(shù)據(jù)庫(kù)慢查詢監(jiān)控內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
10個(gè)Java解決內(nèi)存溢出OOM的方法詳解
在Java開(kāi)發(fā)過(guò)程中,有效的內(nèi)存管理是保證應(yīng)用程序穩(wěn)定性和性能的關(guān)鍵,不正確的內(nèi)存使用可能導(dǎo)致內(nèi)存泄露甚至是致命的OutOfMemoryError(OOM),下面我們就來(lái)學(xué)習(xí)一下有哪些解決辦法吧2024-01-01
java多線程編程之為什么要進(jìn)行數(shù)據(jù)同步
數(shù)據(jù)同步就是指在同一時(shí)間,只能由一個(gè)線程來(lái)訪問(wèn)被同步的類變量,當(dāng)前線程訪問(wèn)完這些變量后,其他線程才能繼續(xù)訪問(wèn),下面看一下為什么要進(jìn)行數(shù)據(jù)同步2014-01-01
Java中super關(guān)鍵字的用法和細(xì)節(jié)
大家好,本篇文章主要講的是Java中super關(guān)鍵字的用法和細(xì)節(jié),感興趣的同學(xué)趕快來(lái)看一看吧,對(duì)你有幫助的話記得收藏一下2022-01-01
Springboot報(bào)錯(cuò)java.lang.NullPointerException: null問(wèn)題
這篇文章主要介紹了Springboot報(bào)錯(cuò)java.lang.NullPointerException: null問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-11-11
JAVA基礎(chǔ)之基本數(shù)據(jù)類型全面解析
下面小編就為大家?guī)?lái)一篇JAVA基礎(chǔ)之基本數(shù)據(jù)類型全面解析。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-07-07
spring boot利用docker構(gòu)建gradle項(xiàng)目的實(shí)現(xiàn)步驟
這篇文章主要給大家介紹了關(guān)于spring boot利用docker構(gòu)建gradle項(xiàng)目的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用spring boot具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2018-05-05
java多線程中執(zhí)行多個(gè)程序的實(shí)例分析
在本篇文章里小編給大家整理的是一篇關(guān)于java多線程中執(zhí)行多個(gè)程序的實(shí)例分析內(nèi)容,有需要的朋友們可以學(xué)習(xí)參考下。2021-02-02
Java8函數(shù)式接口UnaryOperator用法示例
這篇文章主要介紹了Java8函數(shù)式接口UnaryOperator用法示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-07-07

