java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > SpringBoot数据库慢查询监控

SpringBoot实现对数据库慢查询监控的方案小结

作者:风象南

在企业级应用开发中,数据库性能往往是系统整体性能的关键瓶颈,因此,对数据库慢查询进行有效监控和及时优化,是保障系统稳定运行的重要环节,下面我们来看看SpringBoot实现慢查询的6种监控方案吧

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

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

本文将介绍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);
        
        // 处理结果...
    }
}

优缺点分析

优点:

缺点:

适用场景

• 开发和测试环境的问题排查

• 对数据库有完全控制权的场景

• 需要捕获所有数据库操作的场景

• 基础设施层面的监控需求

二、基于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;
    }
}

优缺点分析

优点:

缺点:

适用场景

• 小型应用或并发量不大的系统

• 需要监控特定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-proxydatasource-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;
    }
}

优缺点分析

优点:

缺点:

适用场景

• 中大型微服务架构

• 已经使用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, 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
@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调优

• 需要详细了解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);
    }
}

优缺点分析

优点:

缺点:

适用场景

• 中大型分布式系统

• 微服务架构应用

• 需要完整分布式追踪的场景

• 生产环境监控

• 需要同时监控多种性能指标的场景

六、基于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.datasource.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 new 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");
        
        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;
    }
}

优缺点分析

优点:

缺点:

适用场景

• 对数据库性能有全面监控需求的场景

• 需要开箱即用监控功能的项目

• 小型到中型规模的应用

• 对监控数据安全性有要求的场景

七、方案对比

方案实现复杂度代码侵入性性能影响监控全面性可视化能力
数据库原生慢查询日志
基于AOP的监控
Spring Boot Actuator + Micrometer高(需外部系统)
P6Spy中高
APM工具(SkyWalking等)中高极高
Druid连接池

总结

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

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

到此这篇关于SpringBoot实现对数据库慢查询监控的方案小结的文章就介绍到这了,更多相关SpringBoot数据库慢查询监控内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文