SpringBoot慢查询的6种监控方案(springboot监控平台)

SpringBoot慢查询的6种监控方案(springboot监控平台)

编程文章jaq1232025-08-05 1:18:503A+A-

在企业级应用开发中,数据库性能往往是系统整体性能的关键瓶颈。慢查询不仅会影响用户体验,还可能导致连接池耗尽,进而引发系统雪崩。

因此,对数据库慢查询进行有效监控和及时优化,是保障系统稳定运行的重要环节。

本文将介绍6种在 SpringBoot 应用中实现慢查询监控的方案。

一、数据库原生慢查询日志

原理概述

几乎所有主流关系型数据库都提供了内置的慢查询日志功能,通过设置阈值,将执行时间超过阈值的 SQL 记录到专门的日志文件中。

实现方式

以 MySQL 为例:

1. 配置慢查询日志

修改 MySQL 配置文件( my.cnf ):

# 开启慢查询日志slow_query_log = 1# 慢查询日志文件位置slow_query_log_file = /var/log/mysql/mysql-slow.log# 设置慢查询阈值(秒)long_query_time = 1# 记录没有使用索引的查询log_queries_not_using_indexes = 1

2. 在SpringBoot中查看慢查询日志

@RepositorypublicclassSlowQueryAnalyzer { @Autowired private JdbcTemplate jdbcTemplate; public List<Map<String, Object>> getSlowQueries() { Stringsql="SELECT * FROM mysql.slow_log ORDER BY start_time DESC LIMIT 100"; return jdbcTemplate.queryForList(sql); } publicvoidanalyzeSlowQueries() { Stringsql="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); // 处理结果... }}

优缺点分析

优点:

  • o 零代码侵入,无需修改应用代码
  • o 数据库原生支持,准确性高
  • o 可捕获所有慢查询,包括非应用发起的查询

缺点:

  • o 需要数据库管理员权限配置
  • o 增加数据库I/O负担,生产环境需谨慎使用
  • o 日志分析需要额外工具支持
  • o 无法与应用上下文关联(如调用方法、请求URL等)

适用场景

  • o 开发和测试环境的问题排查
  • o 对数据库有完全控制权的场景
  • o 需要捕获所有数据库操作的场景
  • o 基础设施层面的监控需求

二、基于AOP的慢查询监控

原理概述

利用 Spring AOP 机制,在 Repository 方法执行前后添加切面,计算执行时间并记录超过阈值的方法调用。

实现方式

1. 添加AOP依赖

<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId></dependency>

2. 创建慢查询监控切面

@Aspect@Component@Slf4jpublicclassSlowQueryAspect { @Value("${slow.query.threshold:500}") privatelong slowQueryThreshold; // 默认阈值500毫秒 @Around("execution(* com.example.repository.*.*(..))") public Object around(ProceedingJoinPoint joinPoint)throws Throwable { StringmethodName= joinPoint.getSignature().toShortString(); longstartTime= System.currentTimeMillis(); Objectresult= joinPoint.proceed(); longexecutionTime= System.currentTimeMillis() - startTime; if (executionTime > slowQueryThreshold) { Stringargs= Arrays.toString(joinPoint.getArgs()); log.warn("Slow Query detected: {} with args {}, execution time: {} ms", methodName, args, executionTime); // 可以将慢查询信息保存到数据库或发送告警 saveSlowQueryInfo(methodName, args, executionTime); } return result; } privatevoidsaveSlowQueryInfo(String methodName, String args, long executionTime) { // 保存慢查询信息到数据库或发送到监控系统 }}

3. 创建慢查询事件监听器(可选)

@Component@Slf4jpublicclassSlowQueryEventListener { @Autowired private ApplicationEventPublisher eventPublisher; publicvoidonSlowQuery(String methodName, String args, long executionTime) { SlowQueryEventevent=newSlowQueryEvent(this, methodName, args, executionTime); eventPublisher.publishEvent(event); } @EventListener publicvoidhandleSlowQueryEvent(SlowQueryEvent event) { // 处理慢查询事件,如发送告警邮件、存储到时序数据库等 log.warn("Handling slow query event: {}", event); }}@GetterpublicclassSlowQueryEventextendsApplicationEvent { privatefinal String methodName; privatefinal String args; privatefinallong executionTime; publicSlowQueryEvent(Object source, String methodName, String args, long executionTime) { super(source); this.methodName = methodName; this.args = args; this.executionTime = executionTime; }}

优缺点分析

优点:

o 实现简单,代码侵入性低 o 可以捕获完整的方法调用上下文 o 灵活可定制,可以根据需求调整监控策略 o 可以与应用现有的监控系统集成

缺点:

o 只能监控应用代码中的查询,无法监控原生 SQL o 性能开销较大,特别是在高并发场景 o 可能出现 AOP 失效的场景(如内部方法调用) o 无法获取到实际执行的 SQL 语句

适用场景

o 小型应用或并发量不大的系统 o 需要监控特定 Repository 方法性能的场景 o 开发或测试环境的性能调优 o 已经广泛使用 Spring AOP 的项目

三、Spring Boot Actuator + Micrometer

原理概述

利用 Spring Boot Actuator 和 Micrometer 提供的指标收集功能,监控数据库操作性能,并将数据导出到监控系统。

实现方式

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和数据源监控

application.properties 中添加:

# 开启所有Actuator端点management.endpoints.web.exposure.include=*# 启用数据库指标收集management.metrics.enable.jdbc=true# 配置Prometheus端点management.metrics.export.prometheus.enabled=true

3. 自定义数据源代理,添加指标收集

@ConfigurationpublicclassDataSourceProxyConfig { @Bean @Primary public DataSource dataSource(DataSource originalDataSource, MeterRegistry meterRegistry) { return ProxyDataSourceBuilder .create(originalDataSource) .name("metrics-ds") .listener(newQueryExecutionListener() { @Override publicvoidbeforeQuery(ExecutionInfo execInfo, List<QueryInfo> queryInfoList) { // 查询执行前的操作 } @Override publicvoidafterQuery(ExecutionInfo execInfo, List<QueryInfo> queryInfoList) { longelapsedTime= execInfo.getElapsedTime(); // 记录查询时间指标 Timer.builder("datasource.query.time") .tag("success", String.valueOf(execInfo.isSuccess())) .tag("query", getSafeQueryName(queryInfoList)) .register(meterRegistry) .record(elapsedTime, TimeUnit.MILLISECONDS); // 检测慢查询并记录 if (elapsedTime > 500) { // 500ms 阈值 Counter.builder("datasource.slow.queries") .tag("query", getSafeQueryName(queryInfoList)) .register(meterRegistry) .increment(); // 可以记录慢查询日志 logSlowQuery(execInfo, queryInfoList); } } private String getSafeQueryName(List<QueryInfo> queryInfoList) { if (queryInfoList.isEmpty()) { return"unknown"; } Stringsql= queryInfoList.get(0).getQuery(); // 简化 SQL 以避免过多的唯一标签 return DigestUtils.md5DigestAsHex(sql.getBytes()).substring(0, 8); } privatevoidlogSlowQuery(ExecutionInfo execInfo, List<QueryInfo> queryInfoList) { // 记录慢查询详情 } }) .build(); }}

ProxyDataSourceBuilder 来自开源库 datasource-proxydatasource-proxy 可以用于 JDBC 数据源的代理,可以用来拦截和监控 SQL 查询执行,实现 SQL 日志记录、性能监控、查询统计等功能。

<dependency> <groupId>net.ttddyy</groupId> <artifactId>datasource-proxy</artifactId> <version>1.9</version></dependency>

4. 创建自定义端点查看慢查询

@Component@Endpoint(id = "slowqueries")publicclassSlowQueryEndpoint { @Autowired private MeterRegistry meterRegistry; @ReadOperation public Map<String, Object> slowQueries() { Map<String, Object> result = newHashMap<>(); // 获取慢查询计数器 List<Meter> meters = meterRegistry.getMeters().stream() .filter(m -> m.getId().getName().equals("datasource.slow.queries")) .collect(Collectors.toList()); Map<String, Double> queryCounts = newHashMap<>(); for (Meter meter : meters) { Stringquery= meter.getId().getTag("query"); doublecount= ((Counter) meter).count(); queryCounts.put(query, count); } result.put("counts", queryCounts); // 获取慢查询时间分布 List<Meter> timers = meterRegistry.getMeters().stream() .filter(m -> m.getId().getName().equals("datasource.query.time")) .collect(Collectors.toList()); Map<String, Map<String, Object>> queryTimes = newHashMap<>(); for (Meter meter : timers) { Stringquery= meter.getId().getTag("query"); Timertimer= (Timer) meter; Map<String, Object> stats = newHashMap<>(); 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; }}

优缺点分析

优点:

o 与 Spring Boot 生态紧密集成 o 支持多种监控系统,如 Prometheus、Grafana 等 o 提供丰富的指标和可视化能力 o 运行时监控,影响生产代码较小

缺点:

o 配置相对复杂 o 资源消耗较大,特别是在大量指标收集的情况下 o 需要额外的监控系统支持 o 学习成本较高

适用场景

o 中大型微服务架构 o 已经使用 Prometheus + Grafana 等监控系统的团队 o 需要全面监控系统性能的场景 o 对指标和可视化有较高要求的项目

Python 四、使用P6Spy进行SQL性能监控

原理概述

P6Spy 是一个开源的 JDBC 代理框架,能够拦截 JDBC 操作并记录 SQL 语句的执行情况,包括执行时间、参数等信息。

实现方式

1. 添加依赖

<dependency> <groupId>p6spy</groupId> <artifactId>p6spy</artifactId> <version>3.9.1</version></dependency>

2. 配置数据源

修改数据源配置,将驱动类替换为 P6Spy 的代理驱动:

# 原始配置#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.P6SpyDriverspring.datasource.url=jdbc:p6spy:mysql://localhost:3306/test

3. 创建P6Spy配置文件

resources 目录下创建 spy.properties 文件:

# 指定日志输出模块appender=com.p6spy.engine.spy.appender.Slf4JLogger# 日志格式logMessageFormat=com.example.config.CustomP6SpyLogFormat# 是否开启慢SQL记录outagedetection=true# 慢SQL阈值(毫秒)outagedetectioninterval=2000# 设置 p6spy driver 代理deregisterdrivers=true# 日期格式dateformat=yyyy-MM-dd HH:mm:ss# 实际驱动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;publicclassCustomP6SpyLogFormatimplementsMessageFormattingStrategy { privatestaticfinalDateTimeFormatterformatter= 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. 创建P6Spy慢查询监听器

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@ComponentpublicclassSlowQueryListenerextendsJdbcEventListener { @Value("${sql.slow.threshold:500}") privatelong slowThreshold; // 默认500毫秒 @Override publicvoidonAfterAnyExecute(ConnectionInformation connectionInformation, long timeElapsedNanos, SQLException e) { longtimeElapsedMillis= TimeUnit.NANOSECONDS.toMillis(timeElapsedNanos); if (timeElapsedMillis > slowThreshold) { Stringquery= connectionInformation.getSqlWithValues(); log.warn("Slow SQL detected: {} ms, SQL: {}", timeElapsedMillis, query); // 可以记录到数据库或发送告警 saveSlowQuery(query, timeElapsedMillis); } } privatevoidsaveSlowQuery(String query, long timeElapsed) { // 保存慢查询记录到数据库或告警系统 }}

优缺点分析

优点:

o 能够获取完整的 SQL 语句和参数值 o 配置简单,几乎零代码侵入 o 可以监控所有 JDBC 操作,包括非 ORM 框架的查询 o 提供丰富的自定义选项

缺点:

o 对性能有一定影响,不建议在高负载生产环境长期开启 o 日志量较大,需要合理配置 o 可能与某些特定数据库驱动不兼容 o 不提供内置的图形化监控界面

适用场景

o 开发和测试环境的 SQL 调优 o 需要详细了解 SQL 执行情况的场景 o 排查特定 SQL 问题的临时监控 o 对 SQL 执行参数有监控需求的场景

五、基于APM工具的慢查询监控

原理概述

应用性能监控(APM)工具如 SkyWalking、Pinpoint、Elastic APM 等通过 Java Agent 技术在字节码级别插桩,实现对数据库操作的全方位监控。

实现方式

以 SkyWalking 为例:

1. 下载SkyWalking Agent

从 SkyWalking 官网下载 Agent 包。

2. 配置Java Agent

在启动命令中添加:

java -javaagent:/path/to/skywalking-agent.jar -Dskywalking.agent.service_name=your-service-name -jar your-application.jar

或在 Spring Boot 应用中通过环境变量配置:

# application.ymlspring:  application:    name: your-service-name

3. 配置SkyWalking Agent

修改 agent.config 文件:

# 设置后端服务地址collector.backend_service=localhost:11800# 启用SQL跟踪plugin.jdbc.trace_sql=true# 设置慢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;@ServicepublicclassUserService { @Autowired private UserRepository userRepository; public User findUserById(Long id) { // 添加自定义 Tag ActiveSpan.tag("userId", id.toString()); // 获取 traceId,可用于日志关联 StringtraceId= TraceContext.traceId(); log.info("Processing user query with traceId: {}", traceId); return userRepository.findById(id).orElse(null); }}

5. 使用SkyWalking UI查看慢查询

优缺点分析

优点:

o 全方位监控,包括 HTTP 请求、数据库操作、远程调用等 o 分布式追踪能力,可以跟踪完整调用链 o 零代码侵入(基础功能) o 提供丰富的可视化界面和告警功能 o 支持多种存储后端(ElasticSearch、MySQL 等)

缺点:

o 部署复杂,需要额外维护监控服务器 o 资源消耗较大,增加应用内存占用 o 学习成本较高 o 可能与某些安全策略冲突(如禁止 Java Agent)

适用场景

o 中大型分布式系统 o 微服务架构应用 o 需要完整分布式追踪的场景 o 生产环境监控 o 需要同时监控多种性能指标的场景

六、基于Druid连接池的慢查询监控

原理概述

阿里巴巴开源的 Druid 连接池内置了强大的监控功能,包括慢查询统计、SQL 防火墙等。

实现方式

1. 添加依赖

<dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.2.8</version></dependency>

2. 配置Druid

application.properties 中添加:

# 数据源类型spring.datasource.type=com.alibaba.druid.pool.DruidDataSourcespring.datasource.druid.driver-class-name=com.mysql.cj.jdbc.Driverspring.datasource.druid.url=jdbc:mysql://localhost:3306/testspring.datasource.druid.username=rootspring.datasource.druid.password=password# 连接池配置spring.datasource.druid.initial-size=5spring.datasource.druid.min-idle=5spring.datasource.druid.max-active=20spring.datasource.druid.max-wait=60000# 慢SQL监控配置spring.datasource.druid.filter.stat.enabled=truespring.datasource.druid.filter.stat.log-slow-sql=truespring.datasource.druid.filter.stat.slow-sql-millis=1000spring.datasource.druid.filter.stat.merge-sql=true# 开启监控页面spring.datasource.druid.stat-view-servlet.enabled=truespring.datasource.druid.stat-view-servlet.url-pattern=/druid/*spring.datasource.druid.stat-view-servlet.login-username=adminspring.datasource.druid.stat-view-servlet.login-password=adminspring.datasource.druid.stat-view-servlet.allow=127.0.0.1spring.datasource.druid.stat-view-servlet.deny=# 开启Web应用监控spring.datasource.druid.web-stat-filter.enabled=truespring.datasource.druid.web-stat-filter.url-pattern=/*spring.datasource.druid.web-stat-filter.exclusions=*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*# 开启Spring监控spring.datasource.druid.aop-patterns=com.example.service.*,com.example.repository.*

3. 配置Druid监控(Java Config方式)

@ConfigurationpublicclassDruidConfig { @Bean public ServletRegistrationBean<StatViewServlet> druidStatViewServlet() { ServletRegistrationBean<StatViewServlet> bean = newServletRegistrationBean<>(newStatViewServlet(), "/druid/*"); Map<String, String> initParams = newHashMap<>(); 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 = newFilterRegistrationBean<>(newWebStatFilter()); Map<String, String> initParams = newHashMap<>(); 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() { StatFilterfilter=newStatFilter(); filter.setSlowSqlMillis(1000); filter.setLogSlowSql(true); filter.setMergeSql(true); return filter; } @Bean public DruidStatInterceptor druidStatInterceptor() { returnnewDruidStatInterceptor(); } @Bean public BeanNameAutoProxyCreator druidStatProxyCreator() { BeanNameAutoProxyCreatorcreator=newBeanNameAutoProxyCreator(); creator.setProxyTargetClass(true); creator.setBeanNames("*Service", "*ServiceImpl", "*Repository"); creator.setInterceptorNames("druidStatInterceptor"); return creator; }}

4. 自定义慢查询监听器(可选)

@ComponentpublicclassDruidSlowSqlListenerimplementsApplicationListener<ContextRefreshedEvent> { @Autowired private DruidDataSource dataSource; @Override publicvoidonApplicationEvent(ContextRefreshedEvent event) { dataSource.setConnectionProperties("druid.stat.slowSqlMillis=1000"); StatFilterstatFilter=newStatFilter(); statFilter.setLogSlowSql(true); statFilter.setSlowSqlMillis(1000); statFilter.setMergeSql(true); statFilter.setSlowSqlLoggerName("SLOW_SQL_LOGGER"); dataSource.getProxyFilters().add(statFilter); }}

5. 自定义慢查询Controller(可选)

@RestController@RequestMapping("/api/monitor")publicclassDruidMonitorController { @Autowired private DruidDataSource dataSource; @GetMapping("/slow-sql") public List<Map<String, Object>> getSlowSql() { List<Map<String, Object>> result = newArrayList<>(); try { JdbcStatManagerstatManager= JdbcStatManager.getInstance(); for (Object item : statManager.getDataSourceList().values()) { JdbcDataSourceStatdataSourceStat= (JdbcDataSourceStat) item; Map<String, JdbcSqlStat> sqlStatMap = dataSourceStat.getSqlStatMap(); for (Map.Entry<String, JdbcSqlStat> entry : sqlStatMap.entrySet()) { JdbcSqlStatsqlStat= entry.getValue(); if (sqlStat.getExecuteMillisMax() > 1000) { Map<String, Object> slowSql = newHashMap<>(); 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; }}

优缺点分析

优点:

o 集成度高,开箱即用 o 自带可视化监控界面 o 功能全面,除慢查询外还有连接池监控、SQL 防火墙等

缺点:

o 仅适用于使用 Druid 连接池的场景 o 与其他监控系统集成需要额外开发 o 默认监控页面功能固定,不易扩展 o 安全配置较为重要,否则可能泄露敏感信息

适用场景

o 对数据库性能有全面监控需求的场景 o 需要开箱即用监控功能的项目 o 小型到中型规模的应用 o 对监控数据安全性有要求的场景

七、方案对比

方案对比

| 方案 | 实现复杂度 | 代码侵入性 | 性能影响 | 监控全面性 | 可视化能力 | | ---

| 数据库原生慢查询日志 | 低 | 无 | 中 | 高 | 低 | | 基于 AOP 的监控 | 低 | 低 | 中 | 中 | 低 | | Spring Boot Actuator +

| P6Spy | 低 | 低 | 中高 | 高 | 低 | | APM 工具(SkyWalking 等) | 高 | 低 | 中高 | 极高 | 高 | | Druid 连接池 | 低 | 低 | 低 | 高 | 中 |

总结

慢查询监控是数据库性能优化的重要环节,选择合适的监控方案对于提升应用性能至关重要。

在实际应用中,可以根据项目规模、技术栈和团队能力选择合适的方案,也可以组合使用多种方案,实现更全面的监控覆盖。随着应用的发展,监控策略也应该不断演进和优化,以适应不断变化的性能需求。

后端专属技术群 构建高质量的技术交流社群,欢迎从事编程开发、技术招聘 HR 进群,也欢迎大家分享自己公司的内推信息,相互帮助,一起进步!

文明发言,以 交流技术职位内推行业探讨 为主

广告人士勿入,切勿轻信私聊,防止被骗

加我好友,拉你进群 点下方的 支持我们,非常感谢!

点击这里复制本文地址 以上内容由jaq123整理呈现,请务必在转载分享时注明本文地址!如对内容有疑问,请联系我们,谢谢!

苍茫编程网 © All Rights Reserved.  蜀ICP备2024111239号-21