Web应用并发问题规避:学生订房超订场景下事务与synchronized使用疑问
学生订房系统超订并发问题处理方案
核心疑问解答
不需要把数据库事务代码放到synchronized同步块中,原因如下:
synchronized是JVM进程内的线程锁,仅对单个服务实例的线程有效,如果你部署了多台应用实例,跨实例的并发请求完全不受这个锁限制,依然会出现超订问题- 即使是单实例部署,
synchronized会大幅降低预订接口的并发吞吐量,完全没有必要使用
超订问题具体解决方案
仅开启数据库事务不足以解决超订问题,默认的事务隔离级别无法避免并发修改带来的数据不一致,需要搭配以下方案实现:
方案1:数据库乐观锁(最推荐,性能最优)
适合并发量较高的场景,实现成本极低:
- 给房间信息表新增
version版本号字段,或者直接用剩余可预订数量做校验条件 - 执行预订扣减库存的SQL时增加校验逻辑,示例SQL如下:
UPDATE room SET available_count = available_count - 1, version = version + 1 WHERE room_id = ? AND available_count > 0 AND version = ?
- 执行更新后如果数据库返回的影响行数为0,说明当前房间已被其他用户预订,直接返回预订失败即可
- 整个逻辑仅需要搭配
READ COMMITTED及以上隔离级别的事务即可运行
方案2:数据库悲观锁(适合并发量较低的场景)
实现简单,不需要额外加字段:
- 查询房间信息时直接加行级锁,避免其他事务同时修改,示例SQL如下:
SELECT * FROM room WHERE room_id = ? FOR UPDATE
- 该语句会在事务存续期间锁定对应房间的行记录,其他事务要修改这条数据必须等当前事务提交或回滚,不会出现并发修改冲突
- 注意要控制事务的粒度,不要在事务中执行过多无关逻辑导致锁持有时间过长,影响整体性能
方案3:分布式锁(适合预订流程包含多步复杂业务的场景)
如果预订流程除了操作房间库存,还有扣减用户积分、发送预订通知等多步关联操作,可以用分布式锁做全局控制:
- 进入预订流程前,基于房间ID加分布式锁(可通过Redis SETNX、Redisson等实现)
- 锁的过期时间要设置为大于预订流程的最长耗时,避免锁提前失效导致冲突
- 执行完全部预订逻辑后释放锁即可
内容的提问来源于stack exchange,提问作者daniel
相关产品推荐
相关产品推荐

