SpringBoot实现对数据库慢查询监控的方案小结
目录
- 一、数据库原生慢查询日志
- 原理概述
- 实现方式
- 优缺点分析
- 适用场景
- 二、基于AOP的慢查询监控
- 原理概述
- 实现方式
- 优缺点分析
- 适用场景
- 三、Spring Boot Actuator + Micrometer
- 原理概述
- 实现方式
- 优缺点分析
- 适用场景
- 四、使用P6Spy进行SQL性能监控
- 原理概述
- 实现方式
- 优缺点分析
- 适用场景
- 五、基于APM工具的慢查询监控
- 原理概述
- 实现方式
- 优缺点分析
- 适用场景
- 六、基于Druid连接池的慢查询监控
- 原理概述
- 实现方式
- 优缺点分析
- 适用场景
- 七、方案对比
- 总结
在企业级应用开发中,数据库性能往往是系统整体性能的关键瓶颈。慢查询不仅会影响用户体验,还可能导致连接池耗尽,进而引发系统雪崩。
因此,对数据库慢查询进行有效监控和及时优化,是保障系统稳定运行的重要环节。
本文将介绍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中查看慢查询日志
@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); // 处理结果... } }
优缺点分析
优点:
- 零代码侵入,无需修改应用代码
- 数据库原生支持,准确性高
- 可捕获所有慢查询,包括非应用发起的查询
缺点:
- 需要数据库管理员权限配置
- 增加数据库I/O负担,生产环境需谨慎使用
- 日志分析需要额外工具支持
- 无法与应用上下文关联(如调用方法、请求URL等)
适用场景
• 开发和测试环境的问题排查
• 对数据库有完全控制权的场景
• 需要捕获所有数据库操作的场景
• 基础设施层面的监控需求
二、基于AOP的慢查询监控
原理概述
利用Spring AOP机制,在Repository方法执行前后添加切面,计算执行时间并记录超过阈值的方法调用。
实现方式
1. 添加AOP依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
2. 创建慢查询监控切面
@ASPect @Component @Slf4j public class SlowQueryAspect { @Value("${slow.query.threshold:500}") private long slowQueryThreshold; // 默认阈值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); // 可以将慢查询信息保存到数据库或发送告警 saveSlowQueryInfo(methodName, args, executionTime); } return result; } private void saveSlowQueryInfo(String methodName, String args, long executionTime) { // 保存慢查询信息到数据库或发送到监控系统 } }
3. 创建慢查询事件监听器(可选)
@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) { // 处理慢查询事件,如发送告警邮件、存储到时序数据库等 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; } }
优缺点分析
优点:
- 实现简单,代码侵入性低
- 可以捕获完整的方法调用上下文
- 灵活可定制,可以根据需求调整监控策略
- 可以与应用现有的监控系统集成
缺点:
- 只能监控应用代码中的查询,无法监控原生SQL
- 性能开销较大,特别是在高并发场景
- 可能出现AOP失效的场景(如内部方法调用)
- 无法获取到实际执行的SQL语句
适用场景
• 小型应用或并发量不大的系统
• 需要监控特定Repository方法性能的场景
• 开发或测试环境的性能调优
• 已经广泛使用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. 自定义数据源代理,添加指标收集
@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) { // 查询执行前的操作 } @Override public void afterQuery(ExecutionInfo execInfo, List<QueryInfo> queryInfoList) { long elapsedTime = 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"; } String sql = queryInfoList.get(0).getQuery(); // 简化SQL以避免过多的唯一标签 return DigestUtils.md5DigestAsHex(sql.getBytes()).substring(0, 8); } private void logSlowQuery(ExecutionInfo execInfo, List<QueryInfo> queryInfoList) { // 记录慢查询详情 } }) .build(); } }
ProxyDataSourceBuilder 来自开源库datasource-proxy
,datasource-proxy
可以用于 JDBC 数据源的代理,可以用来拦截和监控 SQL 查询执行,实现 SQL 日志记录、性能监控、查询统计等功能。
<dependency> <groupId>net.ttddyy</groupId> <artifactId>datasource-proxy</artifactId> <version>1.9</version> </dependency>
4. 创建自定义端点查看慢查询
@Component @Endpoint(id = "slowqueries") public class SlowQueryEndpoint { @Autowired private MeterRegistry meterRegistry; @ReadOperation public Map<String, Object> slowQueries() { Map<String, Object> result = new HashMap<>(); // 获取慢查询计数器 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); // 获取慢查询时间分布 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; } }
优缺点分析
优点:
- 与Spring Boot生态紧密集成
- 支持多种监控系统,如Prometheus、Grafana等
- 提供丰富的指标和可视化能力
- 运行时监控,影响生产代码较小
缺点:
- 配置相对复杂
- 资源消耗较大,特别是在大量指标收集的情况下
- 需要额外的监控系统支持
- 学习成本较高
适用场景
• 中大型微服务架构
• 已经使用Prometheus + Grafana等监控系统的团队
• 需要全面监控系统性能的场景
• 对指标和可视化有较高要求的项目
四、使用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.P6SpyDriver spring.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; 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, Striphpng 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 @Component public class SlowQueryListener extends JdbcEventListener { @Value("${sql.slow.threshold:500}") private long slowThreshold; // 默认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); // 可以记录到数据库或发送告警 saveSlowQuery(query, timeElapsedMillis); } } private void saveSlowQuery(String query, long timeElapsed) { // 保存慢查询记录到数据库或告警系统 } }
优缺点分析
优点:
- 能够获取完整的SQL语句和参数值
- 配置简单,几乎零代码侵入
- 可以监控所有JDBC操作,包括非ORM框架的查询
- 提供丰富的自定义选项
缺点:
- 对性能有一定影响,不建议在高负载生产环境长期开启
- 日志量较大,需要合理配置
- 可能与某些特定数据库驱动不兼容
- 不提供内置的图形化监控界面
适用场景
• 开发和测试环境的SQL调优
• 需要详细了解SQL执行情况的场景
• 排查特定SQL问题的临时监控
• 对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.yml spring: 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; @Service public class UserService { @Autowired private UserRepository userRepository; public User findUserById(Long id) { // 添加自定义Tag ActiveSpan.tag("userId", id.toString()); // 获取traceId,可用于日志关联 String traceId = TraceContext.traceId(); log.info("Processing user query with traceId: {}", traceId); return userRepository.findById(id).orElse(null); } }
优缺点分析
优点:
- 全方位监控,包括HTTP请求、数据库操作、远程调用等
- 分布式追踪能力,可以跟踪完整调用链
- 零代码侵入(基础功能)
- 提供丰富的可视化界面和告警功能
- 支持多种存储后端(ElasticSearch、MySQL等)
缺点:
- 部署复杂,需要额外维护监控服务器
- 资源消耗较大,增加应用内存占用
- 学习成本较高
- 可能与某些安全策略冲突(如禁止Java Agent)
适用场景
• 中大型分布式系统
• 微服务架构应用
• 需要完整分布式追踪的场景
• 生产环境监控
• 需要同时监控多种性能指标的场景
六、基于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.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.dathttp://www.devze.comasource.druid.initial-size=5 spring.datasource.druid.min-idle=5 spring.datasource.druid.max-active=20 spring.datasource.druid.max-wait=60000 # 慢SQL监控配置 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 # 开启监控页面 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= # 开启Web应用监控 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/* # 开启Spring监控 spring.datasource.druid.aop-patterns=com.example.service.*,com.example.repository.*
3. 配置Druid监控(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 nwww.devze.comew DruidStatInterceptor(); } @Bean public BeanNameAutoProxyCreator druidStatProxyCreator() { BeanNameAutoProxyCreator creator = new BeanNameAutoProxyCreator(); creator.setProxyTargetClass(true); creator.setBeanNames("*Service", "*ServiceImpl", "*Repository"); creator.setInterceptorNames("druidStatInterceptor"); return creator; } }
4. 自定义慢查询监听器(可选)
@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"); dataSandroidource.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; } }
优缺点分析
优点:
- 集成度高,开箱即用
- 自带可视化监控界面
- 功能全面,除慢查询外还有连接池监控、SQL防火墙等
缺点:
- 仅适用于使用Druid连接池的场景
- 与其他监控系统集成需要额外开发
- 默认监控页面功能固定,不易扩展
- 安全配置较为重要,否则可能泄露敏感信息
适用场景
• 对数据库性能有全面监控需求的场景
• 需要开箱即用监控功能的项目
• 小型到中型规模的应用
• 对监控数据安全性有要求的场景
七、方案对比
方案 | 实现复杂度 | 代码侵入性 | 性能影响 | 监控全面性 | 可视化能力 |
数据库原生慢查询日志 | 低 | 无 | 中 | 高 | 低 |
基于AOP的监控 | 低 | 低 | 中 | 中 | 低 |
Spring Boot Actuator + Micrometer | 中 | 低 | 中 | 高 | 高(需外部系统) |
P6Spy | 低 | 低 | 中高 | 高 | 低 |
APM工具(SkyWalking等) | 高 | 低 | 中高 | 极高 | 高 |
Druid连接池 | 低 | 低 | 低 | 高 | 中 |
总结
慢查询监控是数据库性能优化的重要环节,选择合适的监控方案对于提升应用性能至关重要。
在实际应用中,可以根据项目规模、技术栈和团队能力选择合适的方案,也可以组合使用多种方案,实现更全面的监控覆盖。随着应用的发展,监控策略也应该不断演进和优化,以适应不断变化的性能需求。
到此这篇关于SpringBoot实现对数据库慢查询监控的方案小结的文章就介绍到这了,更多相关SpringBoot数据库慢查询监控内容请搜索编程客栈(www.devze.com)以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程客栈(www.devze.com)!
精彩评论