列车预订系统运作机制及并发订票场景的数据处理问询
列车座位预订系统的运作逻辑与并发冲突处理
一、常规预订流程
正常的高并发预订系统都会有一套防冲突机制,核心流程是:
- 数据库里的
train_seats表会记录每个座位的状态:available(可用)、locked(临时锁定)、booked(已售出) - 用户选座后,系统会把选中的座位标记为
locked,并设置10-15分钟的锁定超时时间——这一步是为了防止其他人同时抢同一个座位 - 用户在锁定时间内完成支付,系统就把座位状态改成
booked,绑定用户ID,生成正式订单 - 如果用户超时没付钱,系统自动把座位改回
available,释放给其他用户
二、你描述的极端场景(无预锁定,两人同时支付)
如果系统没做预锁定,允许两个用户同时选中这2个座位并完成支付,最终结果和数据库更新取决于系统用的并发控制策略:
谁能拿到座位?
只会有一个用户拿到这2个座位,另一个用户的钱会被退回。具体谁能抢到,看数据库的冲突处理逻辑:
- 乐观锁方案:座位表有个
version字段,每次更新都会让版本号+1。第一个完成支付并成功更新数据库的用户(比如用户1)会把座位状态改成booked,版本号也更新了;用户2再更新时,会发现版本号不对,座位也不是可用状态,更新直接失败。 - 悲观锁方案:系统在更新座位时会先给对应的数据库记录加锁。用户1先发起请求,系统锁住这2个座位的记录,完成更新后才释放锁;用户2的请求会等锁释放,等拿到锁时发现座位已经被订了,更新失败。
数据库具体怎么更新?
乐观锁场景
- 用户1的支付请求先落地,执行更新语句:
UPDATE train_seats SET status = 'booked', user_id = 1, version = version + 1 WHERE train_id = 'G123' AND seat_id IN ('A1', 'A2') AND status = 'available' AND version = 0; - 数据库返回“受影响2行”,更新成功,事务提交,同时生成订单表记录(关联用户1和这两个座位)。
- 用户2的更新语句执行时,
version已经变成1,且座位状态是booked,数据库返回“受影响0行”,更新失败。 - 系统触发退款流程,把用户2的钱原路退回,同时在订单表把用户2的订单标记为
失败,新增退款记录。
悲观锁场景
- 用户1的请求先到,系统先执行带锁的查询:
SELECT * FROM train_seats WHERE train_id = 'G123' AND seat_id IN ('A1', 'A2') AND status = 'available' FOR UPDATE; - 数据库锁住这两条座位记录,用户1的事务继续把座位状态改成
booked,绑定用户ID,提交事务后释放锁。 - 用户2的请求执行同样的查询时,会被卡住等待锁释放;锁释放后查询发现座位已经是
booked,更新操作直接失败。 - 同样触发退款,更新用户2的订单状态为
失败,新增退款记录。
三、额外提醒
实际生产环境里不会出现这种情况,因为预锁定是标配——让用户先锁座再付钱,避免付了钱却拿不到座的糟心体验,也能减少退款的运维成本。
内容的提问来源于stack exchange,提问作者Jigar




