在高并发系统中,数据库通常是性能瓶颈。面对高并发请求,我们需要采用合适的优化策略,以保证数据库的稳定性和高效性。本文将介绍数据库高并发问题的成因,并结合 Mybatis-Plus,探讨 乐观锁、悲观锁、高并发优化及数据库连接池优化 的最佳实践。
1. 数据库高并发问题分析
1.1 高并发数据库问题的常见表现
- 数据库连接耗尽:过多的并发请求导致数据库连接池资源被占满,新的请求无法获取连接。
- 锁竞争严重:多个事务对同一行或同一表的数据竞争锁,导致等待时间变长,甚至发生死锁。
- 读写压力过大:业务场景下 大量写操作(INSERT、UPDATE) 或 查询操作(SELECT) 导致数据库压力增大,影响系统响应时间。
- 数据不一致:并发修改同一数据时,可能导致数据丢失或覆盖,产生 脏读、幻读、不可重复读 等问题。
2. 乐观锁与悲观锁的使用
在高并发场景下,数据库并发控制策略主要分为 乐观锁 和 悲观锁。
在高并发场景下,数据库并发控制策略主要分为 乐观锁 和 悲观锁。
2.1 乐观锁
适用场景:适用于 读多写少 的场景,例如 订单支付状态修改、库存扣减 等。
实现方式:
- 通过 版本号机制(Version) 进行更新。
- 在
UPDATE
时,带上version
条件,只有version
匹配时才更新成功,否则更新失败。
Mybatis-Plus 乐观锁实现
- 在实体类中增加
@Version
注解
@Data
@TableName("user")
public class User {
@TableId(type = IdType.AUTO)
private Long id;
private String name;
private Integer age;
@Version // 版本号字段
private Integer version;
}
- 开启 Mybatis-Plus 乐观锁插件
在MybatisPlusConfig
配置类中启用 乐观锁插件:
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return interceptor;
}
}
- 更新时,Mybatis-Plus 自动维护版本号
@Autowired
private UserMapper userMapper;
public void updateUserAge(Long userId) {
User user = userMapper.selectById(userId);
user.setAge(user.getAge() + 1);
userMapper.updateById(user); // Mybatis-Plus 会自动带上 version 字段
}
如果 version 发生变化,更新失败,需要重新读取数据再更新。
2.2 悲观锁
适用场景:适用于 写多读少、强一致性 场景,例如 金融交易、订单扣款 等。
实现方式:
- SELECT … FOR UPDATE:在事务内查询数据并加锁,防止其他事务修改数据。
- 使用数据库本身的行锁(Row Lock)。
示例:使用 FOR UPDATE
进行悲观锁控制
@Select("SELECT * FROM user WHERE id = #{id} FOR UPDATE")
User selectForUpdate(Long id);
- 事务提交前,其他事务无法修改该行数据。
- 适用于事务范围内需要严格一致性的操作。
缺点:
- 如果锁住的数据较多,会导致 大量事务等待,影响并发能力。
3. Mybatis-Plus 高并发优化实践
3.1 批量插入
在高并发写入场景下,逐条 INSERT
可能会导致 SQL 频繁执行,影响性能。Mybatis-Plus 提供了 批量插入 的方式:
@Autowired
private UserMapper userMapper;
public void batchInsertUsers(List<User> users) {
userMapper.insertBatchSomeColumn(users);
}
注意:
insertBatchSomeColumn
需要开启 Mybatis-Plus 扩展插件。
3.2 避免 N+1 查询
问题:当查询列表数据时,可能会引发多次 SQL 查询:
List<Order> orders = orderMapper.selectList(null);
for (Order order : orders) {
User user = userMapper.selectById(order.getUserId());
}
解决方案:
- 使用
IN
查询 一次性获取所有用户数据:
List<Long> userIds = orders.stream().map(Order::getUserId).collect(Collectors.toList());
List<User> users = userMapper.selectBatchIds(userIds);
- 使用 Mybatis-Plus 关联查询
@Select("SELECT o.*, u.name as userName FROM orders o JOIN user u ON o.user_id = u.id WHERE o.id = #{orderId}")
OrderWithUser selectOrderWithUser(Long orderId);
4. 数据库连接池优化
数据库连接池(DataSource)是高并发优化的核心组件,推荐使用 Druid 或 HikariCP。
4.1 HikariCP 连接池优化
在 application.yml
配置:
spring:
datasource:
url: jdbc:mysql://localhost:3306/test
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
hikari:
minimum-idle: 10
maximum-pool-size: 50
idle-timeout: 30000
max-lifetime: 1800000
connection-timeout: 3000
参数解析:
maximum-pool-size
: 连接池最大连接数,通常设置为 CPU 核心数 * 2 + 1。minimum-idle
: 最小空闲连接数。connection-timeout
: 获取连接的超时时间(建议不超过 3 秒)。max-lifetime
: 连接最大存活时间,防止长时间占用连接。
5. 总结
优化点 | 方法 | 适用场景 |
---|---|---|
并发控制 | 乐观锁 @Version |
读多写少,如库存扣减 |
悲观锁 | SELECT ... FOR UPDATE |
高一致性,如订单扣款 |
批量插入 | insertBatchSomeColumn() |
大批量数据插入 |
避免 N+1 查询 | IN 查询、Mybatis-Plus 关联查询 |
避免多次查询 |
连接池优化 | HikariCP | 提高数据库连接管理效率 |
在高并发场景下,数据库优化是一个系统性工程。合理选择 乐观锁/悲观锁、批量插入、数据库连接池优化,可以极大提高数据库吞吐能力,确保系统在高并发下依然稳定高效!🚀