不使用乐观锁与悲观锁,如何解决酒店预订服务方法的并发问题?
可行解决方案
你当前的代码问题在于应用层的库存校验和预订插入不是原子操作,并发场景下多个请求会同时查询到相同的剩余可订数量,都通过校验后重复插入,导致超售。你不需要新增带version的库存实体,也不需要加全局服务锁,用下面的类乐观锁方案即可实现需求:
方案1:数据库原子条件插入(最适配你的场景)
直接把可用性校验逻辑下沉到数据库层面,和预订插入合并为单个原子操作,利用Oracle的事务原子性保证同一时间只有一个请求能插入成功,完全符合你要的「两个请求都可发起,其中一个校验失败抛异常」的要求。
实现逻辑
用INSERT ... SELECT语法编写原生SQL,只有当对应时段对应房型的可订数量满足需求时才执行插入,通过返回的插入行数判断是否预订成功:
- 先调用酒店服务拿到对应房型的总房量(你现有逻辑已经实现)
- 编写Repository层的原生插入方法,示例SQL如下:
INSERT INTO booking (id, hotel_id, check_in_date, checkout_date, 其他你需要的字段) SELECT :bookingId, :hotelId, :checkInDate, :checkoutDate, 其他参数 FROM DUAL WHERE ( -- 计算当前房型的可订数量:总房量 - 对应时段已订数量 :totalRoomsOfType - COALESCE( (SELECT SUM(rbd.no_rooms) FROM booking b JOIN room_booking_details rbd ON b.id = rbd.booking_id WHERE b.hotel_id = :hotelId AND rbd.hotel_room_id = :roomTypeId AND b.check_in_date < :checkoutDate AND b.checkout_date > :checkInDate), 0) ) >= :requiredRoomCount
- 服务层调用上述插入方法后判断返回的影响行数:如果行数为1则预订成功,行数为0直接抛出
OptimisticLockException即可。
优势
- 不需要修改现有表结构,不需要新增带version的实体
- 无应用层锁,并发性能高
- 数据库自动保证操作原子性,不会出现超售
可选方案:分布式锁(多实例部署时的兜底方案)
如果你觉得SQL太复杂不好维护,可以引入Redis分布式锁,锁的粒度为hotelId:roomTypeId:checkInDate:checkoutDate,校验和插入逻辑执行前先抢锁,抢不到直接抛异常。不过这个方案属于悲观锁范畴,并发性能略低于方案1。
注意事项
- Oracle默认的
READ COMMITTED事务隔离级别完全满足需求,不需要调整 - 调用酒店服务拿总房量的逻辑尽量和插入操作紧挨,避免总房量变更导致校验不准
内容的提问来源于stack exchange,提问作者contemplator
相关产品推荐
相关产品推荐

