高并发场景下的数据库优化

发布于:2025-03-05 ⋅ 阅读:(16) ⋅ 点赞:(0)

在高并发系统中,数据库通常是性能瓶颈。面对高并发请求,我们需要采用合适的优化策略,以保证数据库的稳定性和高效性。本文将介绍数据库高并发问题的成因,并结合 Mybatis-Plus,探讨 乐观锁、悲观锁、高并发优化及数据库连接池优化 的最佳实践。


1. 数据库高并发问题分析

1.1 高并发数据库问题的常见表现

  • 数据库连接耗尽:过多的并发请求导致数据库连接池资源被占满,新的请求无法获取连接。
  • 锁竞争严重:多个事务对同一行或同一表的数据竞争锁,导致等待时间变长,甚至发生死锁。
  • 读写压力过大:业务场景下 大量写操作(INSERT、UPDATE)查询操作(SELECT) 导致数据库压力增大,影响系统响应时间。
  • 数据不一致:并发修改同一数据时,可能导致数据丢失或覆盖,产生 脏读、幻读、不可重复读 等问题。

2. 乐观锁与悲观锁的使用

在高并发场景下,数据库并发控制策略主要分为 乐观锁悲观锁
在高并发场景下,数据库并发控制策略主要分为 乐观锁悲观锁

2.1 乐观锁

适用场景:适用于 读多写少 的场景,例如 订单支付状态修改、库存扣减 等。

实现方式

  • 通过 版本号机制(Version) 进行更新。
  • UPDATE 时,带上 version 条件,只有 version 匹配时才更新成功,否则更新失败。

Mybatis-Plus 乐观锁实现

  1. 在实体类中增加 @Version 注解
@Data
@TableName("user")
public class User {
    @TableId(type = IdType.AUTO)
    private Long id;
    private String name;
    private Integer age;
    
    @Version  // 版本号字段
    private Integer version;
}
  1. 开启 Mybatis-Plus 乐观锁插件
    MybatisPlusConfig 配置类中启用 乐观锁插件
@Configuration
public class MybatisPlusConfig {
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
        return interceptor;
    }
}
  1. 更新时,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 提高数据库连接管理效率

在高并发场景下,数据库优化是一个系统性工程。合理选择 乐观锁/悲观锁、批量插入、数据库连接池优化,可以极大提高数据库吞吐能力,确保系统在高并发下依然稳定高效!🚀


网站公告

今日签到

点亮在社区的每一天
去签到