当前位置: 首页 > news >正文

Mybatis学习笔记(八)

性能优化与监控

简要描述:MyBatis-Plus的性能优化涉及SQL优化、缓存策略、连接池配置、监控诊断等多个方面,通过合理的配置和使用可以显著提升应用性能。

核心概念

  • SQL性能分析:分析SQL执行效率
  • 慢查询优化:识别和优化慢查询
  • 缓存策略:合理使用缓存提升性能
  • 连接池优化:优化数据库连接池配置
  • 监控诊断:实时监控和问题诊断

SQL性能分析

简要描述:通过各种工具和插件分析SQL执行性能,识别性能瓶颈。

核心概念

  • 执行计划分析:分析SQL执行计划
  • 性能监控插件:MyBatis-Plus性能监控插件
  • SQL统计:SQL执行统计信息
  • 性能指标:关键性能指标监控

性能监控插件配置

// 性能分析插件
@Configuration
public class PerformanceConfig {@Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor() {MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();// SQL性能规范插件interceptor.addInnerInterceptor(new IllegalSQLInnerInterceptor());// 分页插件PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor(DbType.MYSQL);paginationInnerInterceptor.setMaxLimit(1000L);interceptor.addInnerInterceptor(paginationInnerInterceptor);return interceptor;}@Bean@Profile("dev")public PerformanceInterceptor performanceInterceptor() {PerformanceInterceptor performanceInterceptor = new PerformanceInterceptor();// 设置SQL执行最大时长,超过自动停止运行,有助于发现问题performanceInterceptor.setMaxTime(1000);// 设置SQL格式化,默认falseperformanceInterceptor.setFormat(true);return performanceInterceptor;}
}// 自定义性能监控插件
@Component
public class CustomPerformanceInterceptor implements Interceptor {private static final Logger logger = LoggerFactory.getLogger(CustomPerformanceInterceptor.class);@Overridepublic Object intercept(Invocation invocation) throws Throwable {long startTime = System.currentTimeMillis();try {Object result = invocation.proceed();long endTime = System.currentTimeMillis();long executeTime = endTime - startTime;// 记录执行时间if (executeTime > 100) { // 超过100ms的SQL记录警告MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];Object parameter = invocation.getArgs()[1];logger.warn("慢SQL检测 - 执行时间: {}ms, SQL ID: {}, 参数: {}", executeTime, mappedStatement.getId(), parameter);}return result;} catch (Exception e) {long endTime = System.currentTimeMillis();long executeTime = endTime - startTime;logger.error("SQL执行异常 - 执行时间: {}ms, 异常信息: {}", executeTime, e.getMessage());throw e;}}@Overridepublic Object plugin(Object target) {return Plugin.wrap(target, this);}@Overridepublic void setProperties(Properties properties) {// 设置属性}
}

执行计划分析工具

// 执行计划分析工具
@Component
public class ExecutionPlanAnalyzer {@Autowiredprivate SqlSessionFactory sqlSessionFactory;public List<ExecutionPlan> analyzeExecutionPlan(String sql, Object... params) {try (SqlSession sqlSession = sqlSessionFactory.openSession()) {// 执行EXPLAIN分析String explainSql = "EXPLAIN " + sql;List<Map<String, Object>> results = sqlSession.selectList("analyzeExecutionPlan", Map.of("sql", explainSql, "params", params));return results.stream().map(this::convertToExecutionPlan).collect(Collectors.toList());}}private ExecutionPlan convertToExecutionPlan(Map<String, Object> result) {ExecutionPlan plan = new ExecutionPlan();plan.setId((Integer) result.get("id"));plan.setSelectType((String) result.get("select_type"));plan.setTable((String) result.get("table"));plan.setType((String) result.get("type"));plan.setPossibleKeys((String) result.get("possible_keys"));plan.setKey((String) result.get("key"));plan.setKeyLen((String) result.get("key_len"));plan.setRef((String) result.get("ref"));plan.setRows((Long) result.get("rows"));plan.setExtra((String) result.get("Extra"));return plan;}public boolean hasPerformanceIssues(List<ExecutionPlan> plans) {for (ExecutionPlan plan : plans) {// 检查是否有性能问题if ("ALL".equals(plan.getType()) || // 全表扫描plan.getRows() > 10000 || // 扫描行数过多plan.getExtra().contains("Using filesort") || // 文件排序plan.getExtra().contains("Using temporary")) { // 使用临时表return true;}}return false;}
}

慢查询优化

简要描述:识别、分析和优化慢查询,提升数据库查询性能。

核心概念

  • 慢查询日志:记录执行时间超过阈值的SQL
  • 索引优化:合理创建和使用索引
  • 查询重写:优化SQL语句结构
  • 分页优化:优化大数据量分页查询

慢查询监控配置

# application.yml
spring:datasource:druid:# 慢SQL记录filter:stat:enabled: trueslow-sql-millis: 1000log-slow-sql: true# 监控配置stat-view-servlet:enabled: trueurl-pattern: /druid/*reset-enable: falseweb-stat-filter:enabled: trueurl-pattern: /*exclusions: "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*"# MyBatis-Plus慢查询配置
mybatis-plus:configuration:# 开启SQL日志log-impl: org.apache.ibatis.logging.slf4j.Slf4jImplglobal-config:# 性能分析插件enable-sql-runner: truelogging:level:# 开启SQL日志com.example.mapper: debug# Druid慢SQL日志druid.sql.Statement: debug

慢查询优化策略

// 慢查询优化服务
@Service
public class SlowQueryOptimizationService {@Autowiredprivate UserMapper userMapper;// 优化前:全表扫描public List<User> findUsersByNameBad(String name) {QueryWrapper<User> wrapper = new QueryWrapper<>();wrapper.like("username", "%" + name + "%"); // 前缀模糊查询,无法使用索引return userMapper.selectList(wrapper);}// 优化后:使用索引public List<User> findUsersByNameGood(String name) {QueryWrapper<User> wrapper = new QueryWrapper<>();wrapper.likeRight("username", name); // 右模糊查询,可以使用索引return userMapper.selectList(wrapper);}// 优化前:N+1查询问题public List<UserWithRoles> findUsersWithRolesBad() {List<User> users = userMapper.selectList(null);return users.stream().map(user -> {List<Role> roles = roleMapper.selectByUserId(user.getId()); // N+1查询return new UserWithRoles(user, roles);}).collect(Collectors.toList());}// 优化后:批量查询public List<UserWithRoles> findUsersWithRolesGood() {List<User> users = userMapper.selectList(null);if (users.isEmpty()) {return Collections.emptyList();}List<Long> userIds = users.stream().map(User::getId).collect(Collectors.toList());// 批量查询角色List<UserRole> userRoles = userRoleMapper.selectList(new QueryWrapper<UserRole>().in("user_id", userIds));// 构建用户角色映射Map<Long, List<Role>> userRoleMap = userRoles.stream().collect(Collectors.groupingBy(UserRole::getUserId,Collectors.mapping(ur -> roleMapper.selectById(ur.getRoleId()),Collectors.toList())));return users.stream().map(user -> new UserWithRoles(user, userRoleMap.getOrDefault(user.getId(), Collections.emptyList()))).collect(Collectors.toList());}// 分页查询优化public IPage<User> findUsersWithPagination(int current, int size, String keyword) {Page<User> page = new Page<>(current, size);QueryWrapper<User> wrapper = new QueryWrapper<>();if (StringUtils.hasText(keyword)) {wrapper.and(w -> w.like("username", keyword).or().like("email", keyword));}// 优化:只查询必要字段wrapper.select("id", "username", "email", "create_time");return userMapper.selectPage(page, wrapper);}// 大数据量分页优化(游标分页)public List<User> findUsersWithCursorPagination(Long lastId, int size) {QueryWrapper<User> wrapper = new QueryWrapper<>();if (lastId != null) {wrapper.gt("id", lastId);}wrapper.orderByAsc("id");wrapper.last("LIMIT " + size);return userMapper.selectList(wrapper);}
}

索引优化建议

-- 创建合适的索引
-- 1. 单列索引
CREATE INDEX idx_user_username ON user(username);
CREATE INDEX idx_user_email ON user(email);
CREATE INDEX idx_user_create_time ON user(create_time);-- 2. 复合索引(注意字段顺序)
CREATE INDEX idx_user_status_create_time ON user(status, create_time);
CREATE INDEX idx_user_dept_status ON user(dept_id, status);-- 3. 覆盖索引(包含查询所需的所有字段)
CREATE INDEX idx_user_cover ON user(status, username, email, create_time);-- 4. 前缀索引(对于长字符串字段)
CREATE INDEX idx_user_description ON user(description(50));-- 5. 函数索引(MySQL 8.0+)
CREATE INDEX idx_user_upper_username ON user((UPPER(username)));

缓存优化策略

简要描述:合理使用MyBatis一级缓存、二级缓存以及外部缓存系统,提升查询性能。

核心概念

  • 一级缓存:SqlSession级别的缓存
  • 二级缓存:Mapper级别的缓存
  • 外部缓存:Redis等外部缓存系统
  • 缓存策略:缓存的使用策略和失效机制

MyBatis缓存配置

# application.yml
mybatis-plus:configuration:# 开启二级缓存cache-enabled: true# 本地缓存作用域local-cache-scope: session# 懒加载配置lazy-loading-enabled: trueaggressive-lazy-loading: false

Redis缓存集成

// Redis缓存配置
@Configuration
@EnableCaching
public class CacheConfig {@Beanpublic RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {RedisTemplate<String, Object> template = new RedisTemplate<>();template.setConnectionFactory(factory);// 设置序列化器Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class);ObjectMapper mapper = new ObjectMapper();mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);serializer.setObjectMapper(mapper);template.setKeySerializer(new StringRedisSerializer());template.setHashKeySerializer(new StringRedisSerializer());template.setValueSerializer(serializer);template.setHashValueSerializer(serializer);template.afterPropertiesSet();return template;}@Beanpublic CacheManager cacheManager(RedisConnectionFactory factory) {RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofMinutes(30)).serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer())).serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer())).disableCachingNullValues();return RedisCacheManager.builder(factory).cacheDefaults(config).build();}
}

Spring Cache注解使用

@Service
public class UserService {@Autowiredprivate UserMapper userMapper;// 缓存查询结果@Cacheable(value = "users", key = "#id")public User findById(Long id) {return userMapper.selectById(id);}// 缓存查询结果(条件缓存)@Cacheable(value = "users", key = "#username", condition = "#username.length() > 3")public User findByUsername(String username) {QueryWrapper<User> wrapper = new QueryWrapper<>();wrapper.eq("username", username);return userMapper.selectOne(wrapper);}// 更新时清除缓存@CacheEvict(value = "users", key = "#user.id")public void updateUser(User user) {userMapper.updateById(user);}// 删除时清除缓存@CacheEvict(value = "users", key = "#id")public void deleteUser(Long id) {userMapper.deleteById(id);}// 清除所有缓存@CacheEvict(value = "users", allEntries = true)public void clearAllCache() {// 清除所有用户缓存}// 更新缓存@CachePut(value = "users", key = "#user.id")public User saveUser(User user) {userMapper.insert(user);return user;}
}

连接池优化

简要描述:优化数据库连接池配置,提升数据库连接效率和应用性能。

核心概念

  • 连接池大小:合理设置连接池大小
  • 连接超时:设置合适的连接超时时间
  • 连接验证:连接有效性验证
  • 连接监控:连接池状态监控

Druid连接池优化配置

# application.yml
spring:datasource:type: com.alibaba.druid.pool.DruidDataSourcedruid:# 初始连接数initial-size: 10# 最小空闲连接数min-idle: 10# 最大活跃连接数max-active: 100# 获取连接等待超时时间max-wait: 60000# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒time-between-eviction-runs-millis: 60000# 配置一个连接在池中最小生存的时间,单位是毫秒min-evictable-idle-time-millis: 300000# 配置一个连接在池中最大生存的时间,单位是毫秒max-evictable-idle-time-millis: 900000# 用来检测连接是否有效的sql,要求是一个查询语句validation-query: SELECT 1# 建议配置为true,不影响性能,并且保证安全性test-while-idle: true# 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能test-on-borrow: false# 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能test-on-return: false# 是否缓存preparedStatement,也就是PSCachepool-prepared-statements: true# 要启用PSCache,必须配置大于0,当大于0时,poolPreparedStatements自动触发修改为truemax-pool-prepared-statement-per-connection-size: 20# 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙filters: stat,wall,slf4j# 通过connectProperties属性来打开mergeSql功能;慢SQL记录connection-properties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=2000# 合并多个DruidDataSource的监控数据use-global-data-source-stat: true# 配置web监控web-stat-filter:enabled: trueurl-pattern: /*exclusions: "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*"session-stat-enable: falsesession-stat-max-count: 1000principal-session-name: adminprincipal-cookie-name: adminprofile-enable: true# 配置监控页面stat-view-servlet:enabled: trueurl-pattern: /druid/*# IP白名单(没有配置或者为空,则允许所有访问)allow: 127.0.0.1,192.168.163.1# IP黑名单 (存在共同时,deny优先于allow)deny: 192.168.1.73# 禁用HTML页面上的"Reset All"功能reset-enable: false# 登录名login-username: admin# 登录密码login-password: 123456

HikariCP连接池优化配置

# application.yml
spring:datasource:type: com.zaxxer.hikari.HikariDataSourcehikari:# 连接池名称pool-name: HikariCP# 最小空闲连接数minimum-idle: 10# 最大连接池大小maximum-pool-size: 100# 自动提交auto-commit: true# 空闲连接存活最大时间,默认600000(10分钟)idle-timeout: 600000# 连接池最大生命周期,0表示无限生命周期,默认1800000即30分钟max-lifetime: 1800000# 连接超时时间,默认30000即30秒connection-timeout: 30000# 测试连接是否可用的查询语句connection-test-query: SELECT 1# 连接初始化SQLconnection-init-sql: SET NAMES utf8mb4# 数据库连接超时时间,默认30秒,即30000validation-timeout: 5000# 空闲连接检测周期,默认30000毫秒keepalive-time: 30000# 是否允许连接泄露检测leak-detection-threshold: 60000

连接池监控

// 连接池监控服务
@Service
public class DataSourceMonitorService {@Autowiredprivate DataSource dataSource;public DataSourceStats getDataSourceStats() {if (dataSource instanceof DruidDataSource) {return getDruidStats((DruidDataSource) dataSource);} else if (dataSource instanceof HikariDataSource) {return getHikariStats((HikariDataSource) dataSource);}return new DataSourceStats();}private DataSourceStats getDruidStats(DruidDataSource druidDataSource) {DataSourceStats stats = new DataSourceStats();stats.setActiveCount(druidDataSource.getActiveCount());stats.setPoolingCount(druidDataSource.getPoolingCount());stats.setMaxActive(druidDataSource.getMaxActive());stats.setCreateCount(druidDataSource.getCreateCount());stats.setDestroyCount(druidDataSource.getDestroyCount());stats.setConnectCount(druidDataSource.getConnectCount());stats.setCloseCount(druidDataSource.getCloseCount());return stats;}private DataSourceStats getHikariStats(HikariDataSource hikariDataSource) {DataSourceStats stats = new DataSourceStats();HikariPoolMXBean poolBean = hikariDataSource.getHikariPoolMXBean();stats.setActiveCount(poolBean.getActiveConnections());stats.setPoolingCount(poolBean.getIdleConnections());stats.setMaxActive(hikariDataSource.getMaximumPoolSize());stats.setTotalConnections(poolBean.getTotalConnections());return stats;}@Scheduled(fixedRate = 30000) // 每30秒监控一次public void monitorDataSource() {DataSourceStats stats = getDataSourceStats();// 记录监控日志logger.info("数据源监控 - 活跃连接: {}, 空闲连接: {}, 最大连接: {}", stats.getActiveCount(), stats.getPoolingCount(), stats.getMaxActive());// 检查连接池健康状况if (stats.getActiveCount() > stats.getMaxActive() * 0.8) {logger.warn("连接池使用率过高: {}%", (double) stats.getActiveCount() / stats.getMaxActive() * 100);}}
}

监控与诊断

简要描述:通过各种监控工具和诊断手段,实时监控MyBatis-Plus应用的性能状况。

核心概念

  • 性能指标监控:关键性能指标的实时监控
  • 健康检查:应用健康状况检查
  • 链路追踪:分布式链路追踪
  • 告警机制:异常情况告警

Actuator监控配置

# application.yml
management:endpoints:web:exposure:include: "*"endpoint:health:show-details: alwaysmetrics:enabled: truemetrics:export:prometheus:enabled: truedistribution:percentiles-histogram:http.server.requests: truepercentiles:http.server.requests: 0.5, 0.9, 0.95, 0.99

自定义健康检查

// 数据库健康检查
@Component
public class DatabaseHealthIndicator implements HealthIndicator {@Autowiredprivate SqlSessionFactory sqlSessionFactory;@Overridepublic Health health() {try (SqlSession sqlSession = sqlSessionFactory.openSession()) {// 执行简单查询测试数据库连接sqlSession.selectOne("SELECT 1");return Health.up().withDetail("database", "Available").withDetail("validationQuery", "SELECT 1").build();} catch (Exception e) {return Health.down().withDetail("database", "Unavailable").withDetail("error", e.getMessage()).build();}}
}// MyBatis-Plus健康检查
@Component
public class MybatisPlusHealthIndicator implements HealthIndicator {@Autowiredprivate UserMapper userMapper;@Overridepublic Health health() {try {// 测试基本CRUD操作long count = userMapper.selectCount(null);return Health.up().withDetail("mybatis-plus", "Available").withDetail("userCount", count).build();} catch (Exception e) {return Health.down().withDetail("mybatis-plus", "Unavailable").withDetail("error", e.getMessage()).build();}}
}

性能指标收集

// 自定义性能指标
@Component
public class MybatisPlusMetrics {private final MeterRegistry meterRegistry;private final Counter sqlExecutionCounter;private final Timer sqlExecutionTimer;private final AtomicLong slowQueryCount = new AtomicLong(0);public MybatisPlusMetrics(MeterRegistry meterRegistry) {this.meterRegistry = meterRegistry;this.sqlExecutionCounter = Counter.builder("mybatis.sql.executions").description("Total SQL executions").register(meterRegistry);this.sqlExecutionTimer = Timer.builder("mybatis.sql.duration").description("SQL execution duration").register(meterRegistry);Gauge.builder("mybatis.sql.slow.count").description("Number of slow queries").register(meterRegistry, this, MybatisPlusMetrics::getSlowQueryCount);}public void recordSqlExecution(String sqlId, long duration) {sqlExecutionCounter.increment(Tags.of("sql.id", sqlId,"status", "success"));sqlExecutionTimer.record(duration, TimeUnit.MILLISECONDS,Tags.of("sql.id", sqlId));}public void recordSqlError(String sqlId, String errorType) {sqlExecutionCounter.increment(Tags.of("sql.id", sqlId,"status", "error","error.type", errorType));}private double getSlowQueryCount() {return slowQueryCount.get();}public void incrementSlowQueryCount() {slowQueryCount.incrementAndGet();}
}
http://www.dtcms.com/a/331309.html

相关文章:

  • VS2022 C++生成和调用DLL动态链接库
  • 小杰python高级(six day)——pandas库
  • 自由学习记录(84)
  • nnDetection在windows系统下使用教程
  • 4.Ansible部署文件到主机
  • Torch -- 卷积学习day2 -- 卷积扩展、数据集、模型
  • Linux软件编程(四)多任务与多进程管理
  • 机械硬盘模块逻辑与工作原理
  • 某处卖600的【独角仙】尾盘十分钟短线 尾盘短线思路 手机电脑通用无未来函数
  • uniapp对接极光消息推送
  • 【CLR via C#(第3版)阅读笔记】类型基础
  • [特殊字符]走进华为,解锁商业传奇密码
  • K8s学习----Namespace:资源隔离与环境管理的核心机制
  • 渲染 opentype 多个字符的文本,并设置文本的渲染开始位置
  • Warm-Flow 1.8.0 重大更新
  • Lua 脚本在 Redis 中的应用
  • vivo Pulsar 万亿级消息处理实践(4)-Ansible运维部署
  • 河南萌新联赛2025第(五)场:信息工程大学补题
  • 飞书文档定时自动同步至百炼知识库
  • ESP32 I2S音频总线学习笔记(六):DIY蓝牙音箱教程
  • CVPR 2025 | 北大团队SLAM3R:单目RGB长视频实时重建,精度效率双杀!
  • 在mysql> 下怎么运行 .sql脚本
  • C#WPF实战出真汁00--项目介绍
  • 极速开发新体验_Vite构建工具详解
  • 使用YOLOv13进行钢板表面缺陷检测
  • Python之Django使用技巧(附视频教程)
  • 云手机都具有哪些特点?
  • Ollama如何分别使用2张H100GPU和4张A100部署GPT-OSS-120B全指南:硬件配置与负载均衡实战
  • Linux命令大全-zip命令
  • 嵌入式学习(day27)多任务进程