第3篇-基于源码的调优实践
约 2465 字大约 8 分钟
2026-05-24
4. 设置合适的事务只读属性
你可能会问:为什么要设置只读属性呢?
答案:为了优化性能!数据库会对只读事务进行优化(比如,避免脏读、不可重复读等)。
推荐:对于查询操作,设置为只读!
示例:
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
@Transactional(readOnly = true) // 【核心】只读事务
public User getUserById(Long id) {
return userMapper.selectById(id);
}
}二、数据源调优
数据源调优是第二重要的调优!因为数据源直接影响到连接池的性能。
1. 配置合适的最大连接数(maxActive)
你可能会问:如何配置最大连接数呢?
答案:看你的业务并发量!
推荐配置:
- 如果是小型应用(并发量 < 100):
maxActive = 50 - 如果是中型应用(并发量 100 ~ 500):
maxActive = 100 - 如果是大型应用(并发量 > 500):
maxActive = 200(或者更多)
示例(Druid 连接池):
# application.yml
spring:
datasource:
druid:
# 【核心】最大连接数
max-active: 100【注意】 最大连接数不是越大越好!因为:
- 数据库服务器的资源是有限的(比如 CPU、内存、带宽)
- 连接数太多会导致数据库服务器负载过高
2. 配置合适的最小空闲连接数(minIdle)
你可能会问:为什么要配置最小空闲连接数呢?
答案:为了避免频繁创建/销毁连接!如果有足够的空闲连接,就可以直接获取,不需要创建新连接。
推荐配置:最小空闲连接数 = 最大连接数 / 2
示例(Druid 连接池):
# application.yml
spring:
datasource:
druid:
# 【核心】最小空闲连接数
min-idle: 503. 配置合适的连接获取超时时间(maxWait)
你可能会问:为什么要配置连接获取超时时间呢?
答案:为了避免线程无限等待!如果连接池已经耗尽,线程会一直等待,直到获取到连接为止。这会导致线程阻塞!
推荐配置:最大等待时间 = 30 秒
示例(Druid 连接池):
# application.yml
spring:
datasource:
druid:
# 【核心】连接获取超时时间(毫秒)
max-wait: 300004. 开启连接泄漏检测(removeAbandoned)
你可能会问:为什么要开启连接泄漏检测呢?
答案:为了避免连接泄漏!如果连接泄漏了,连接池中的连接会越来越少,最终耗尽!
推荐配置:开启连接泄漏检测!
示例(Druid 连接池):
# application.yml
spring:
datasource:
druid:
# 【核心】是否自动回收泄漏的连接
remove-abandoned: true
# 【核心】连接泄漏的超时时间(秒)
remove-abandoned-timeout: 300
# 【核心】是否打印连接泄漏的日志
log-abandoned: true三、MyBatis 调优
MyBatis 调优是第三重要的调优!因为 MyBatis 直接影响到SQL 执行性能。
1. 使用 PreparedStatement(默认就是)
你可能会问:为什么要使用 PreparedStatement 呢?
答案:因为 PreparedStatement 可以:
- 预编译 SQL:提高执行效率
- 防止 SQL 注入:参数化查询
- 支持动态参数:
?占位符
【注意】 MyBatis 默认就使用 PreparedStatement!所以,你不需要做任何配置。
2. 使用批量操作(BatchExecutor)
你可能会问:为什么要使用批量操作呢?
答案:为了提高批量插入/更新/删除的性能!如果不使用批量操作,每一条 SQL 都会单独执行,性能很差。
推荐场景:批量插入/更新/删除
示例一:使用 MyBatis 的 BatchExecutor
@Servce
public class UserService {
@Autowired
private SqlSessionTemplate sqlSessionTemplate;
public void batchInsert(List<User> users) {
// 【核心】使用 BatchExecutor
SqlSession sqlSession = sqlSessionTemplate.getSqlSessionFactory().openSession(ExecutorType.BATCH);
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
for (User user : users) {
userMapper.insert(user);
}
sqlSession.commit();
sqlSession.close();
}
}示例二:使用 MyBatis-Spring 的 BatchSqlSessionTemplate
@Servce
public class UserService {
@Autowired
private UserMapper userMapper;
@Transactional
public void batchInsert(List<User> users) {
for (User user : users) {
userMapper.insert(user);
}
// 【注意】需要在 MyBatis 配置中设置 ExecutorType.BATCH
}
}【扩展点】 你可以在 MyBatis 配置中设置默认的执行器类型:
# application.yml
mybatis:
executor-type: BATCH # 【核心】默认使用 BatchExecutor3. 使用一级缓存(默认开启)
你可能会问:为什么要使用一级缓存呢?
答案:为了提高查询性能!如果同一个 SqlSession 中执行了相同的查询,MyBatis 会直接从一级缓存中获取结果,不需要再查询数据库。
【注意】 MyBatis 默认就开启一级缓存!所以,你不需要做任何配置。
但是,一级缓存有以下限制:
- 只能在同一个 SqlSession 中生效
- 如果执行了 INSERT/UPDATE/DELETE,一级缓存会清空
4. 使用二级缓存(谨慎使用!)
你可能会问:为什么要使用二级缓存呢?
答案:为了提高跨 SqlSession 的查询性能!如果多个 SqlSession 执行了相同的查询,MyBatis 会直接从二级缓存中获取结果,不需要再查询数据库。
但是,二级缓存有以下问题:
- 数据不一致:如果多个应用实例共享同一个数据库,二级缓存会导致数据不一致
- 内存溢出:如果缓存的数据量太大,会导致内存溢出
所以,不建议使用二级缓存!推荐使用 Redis 等分布式缓存。
如果你一定要使用二级缓存,配置如下:
<!-- UserMapper.xml -->
<mapper namespace="com.example.mapper.UserMapper">
<!-- 【核心】开启二级缓存 -->
<cache/>
<select id="selectById" resultType="User">
SELECT * FROM user WHERE id = #{id}
</select>
</mapper>5. 使用分页插件(PageHelper)
你可能会问:为什么要使用分页插件呢?
答案:为了提高分页查询的性能!如果你自己写分页 SQL,可能会写出性能很差的 SQL(比如 SELECT * FROM user LIMIT 1000000, 10,这会导致全表扫描)。
推荐:使用 PageHelper 分页插件!
示例:
@Servce
public class UserService {
@Autowired
private UserMapper userMapper;
public PageInfo<User> getUsersByPage(int pageNum, int pageSize) {
// 【核心】开启分页
PageHelper.startPage(pageNum, pageSize);
List<User> users = userMapper.selectAll();
// 【核心】获取分页信息
PageInfo<User> pageInfo = new PageInfo<>(users);
return pageInfo;
}
}四、MySQL 调优
MySQL 调优是第四重要的调优!因为 MySQL 直接影响到SQL 执行性能。
1. 创建合适的索引
你可能会问:为什么要创建索引呢?
答案:为了提高查询性能!如果没有索引,MySQL 会进行全表扫描,性能很差。
推荐索引:
- 主键索引(默认就有)
- 唯一索引(比如
username) - 普通索引(比如
email) - 组合索引(比如 ame + age`)
示例:
-- 【核心】创建普通索引
CREATE INDEX idx_email ON user(email);
-- 【核心】创建组合索引
CREATE INDEX idx_name_age ON user(name, age);【注意】 索引不是越多越好!因为:
- 索引会占用磁盘空间
- 索引会降低 INSERT/UPDATE/DELETE 的性能(因为需要维护索引)
2. 分析慢查询(Slow Query Log)
你可能会问:如何找到性能差的 SQL 呢?
答案:开启 MySQL 的慢查询日志!
配置慢查询日志:
-- 【核心】开启慢查询日志
SET GLOBAL slow_query_log = 'ON';
-- 【核心】设置慢查询阈值(秒)
SET GLOBAL long_query_time = 1;
-- 【核心】查看慢查询日志文件路径
SHOW VARIABLES LIKE 'slow_query_log_file';分析慢查询日志:
# 【核心】使用 mysqldumpslow 分析慢查询日志
mysqldumpslow -s t -t 10 /var/lib/mysql/slow.log3. 使用 EXPLAIN 分析 SQL 执行计划
你可能会问:如何分析 SQL 的执行计划呢?
答案:使用 EXPLAIN 命令!
示例:
-- 【核心】分析 SQL 执行计划
EXPLAIN SELECT * FROM user WHERE email = 'test@example.com';关键字段:
- type:访问类型(比如
ALL(全表扫描)、index(索引扫描)、range(范围扫描)、ref(非唯一索引扫描)、eq_ref(唯一索引扫描)、const(常量查询)) - key:实际使用的索引
- rows:扫描的行数
- Extra:额外信息(比如
Using index(覆盖索引)、Using where(使用了 WHERE 条件)、Using temporary(使用了临时表)、Using filesort(使用了文件排序))
【注意】 如果 type = ALL,说明进行了全表扫描,需要优化!
五、总结与下一篇预告
1. 总结
今天,我们详细剖析了基于源码的调优实践,包括:
- 事务调优:选择合适的事务传播行为、隔离级别、超时时间、只读属性
- 数据源调优:配置合适的最大连接数、最小空闲连接数、连接获取超时时间、开启连接泄漏检测
- MyBatis 调优:使用 PreparedStatement、批量操作、一级缓存、二级缓存、分页插件
- MySQL 调优:创建合适的索引、分析慢查询、使用 EXPLAIN 分析 SQL 执行计划
你可能会问:我知道了调优方法,但是如何监控性能呢?
别着急!在下一篇文章中,我们会详细讲解那些年我们踩过的事务坑,包括:
- 自调用问题
- 异常不回滚
- 连接泄漏
- 死锁
- 等等
2. 下一篇预告
在下一篇(系列四第 4 篇)中,我们会讲解 《那些年我们踩过的事务坑》。
你会学到:
- 自调用问题(为什么 @Transactional 不生效?)
- 异常不回滚(为什么事务没有回滚?)
- 连接泄漏(为什么连接池耗尽了?)
- 死锁(为什么 MySQL 报死锁错误?)
- 等等
你可能会问:为什么需要讲这些坑呢?
问得好! 因为在实际工作中,踩坑才是常态!你需要:
- 快速定位问题:知道每个坑的现象
- 快速解决问题:知道如何填坑
所以,下一篇文章会是非常实用、非常有收获的!相信我!😎
写到这里,我不禁感叹:调优是一个持续的过程!你需要不断地监控性能、分析瓶颈、进行调优。
但是,只要你掌握了这些调优方法,你就能够快速定位性能瓶颈、快速解决性能问题!
好了,今天的文章就到这里。如果你觉得有收获,别忘了点赞、收藏、关注哦!你的支持,是我创作的最大动力!❤️
我们下一篇见!👋