You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Laravel场景下如何仅锁定可用数据集中指定数量的行?

票务系统座位分配锁优化方案

核心问题拆解

原代码的性能瓶颈在于lockForUpdate锁定了所有符合条件的可用座位,而非仅要分配的N个。这会导致高峰时大量事务等待同一批锁,吞吐量急剧下降。以下是几种可行的优化方案:


方案1:精准锁定待分配座位(最直接的优化)

给查询添加明确的排序规则,让数据库只锁定需要分配的N个座位,而非扫描并锁定所有可用座位。

// 开启事务
$assignedSeats = VenueSeat::available()
    ->where('event_id', 1)
    ->orderBy('seat_id') // 按座位唯一标识排序,确保数据库精准定位前N个
    ->take(2)
    ->lockForUpdate()
    ->get();

// 仅更新已锁定的座位状态
VenueSeat::whereIn('id', $assignedSeats->pluck('id'))
    ->update(['status' => 'booked']);

// 提交事务

原理:添加orderBy后,数据库会利用索引快速定位到符合条件的前N个座位,只会锁定这几行数据,锁范围大幅缩小,避免不必要的资源占用。同时事务内的锁定机制确保其他请求无法修改这些座位,从根源避免重复预订。


方案2:跳过锁定行,提升并发吞吐量(适合高并发场景)

如果你的数据库支持SKIP LOCKED(MySQL 8.0+、PostgreSQL 9.5+),可以直接跳过已被其他事务锁定的座位,无需等待,直接获取可用座位。

// 开启事务
$assignedSeats = VenueSeat::available()
    ->where('event_id', 1)
    ->orderBy('seat_id')
    ->take(2)
    ->lockForUpdate()
    ->skipLocked() // 跳过已被锁定的座位,直接取可用的
    ->get();

// 检查是否获取到足够座位
if ($assignedSeats->count() < 2) {
    DB::rollBack();
    // 返回座位不足的提示
    return ['code' => 400, 'msg' => '剩余座位不足'];
}

// 更新座位状态
VenueSeat::whereIn('id', $assignedSeats->pluck('id'))
    ->update(['status' => 'booked']);

// 提交事务

优势:避免了事务间的等待阻塞,高并发下能显著提升系统吞吐量,用户不会因为锁等待而长时间卡顿。


方案3:批次化锁范围(适合超大型场次)

如果单场次座位数量极多(比如上千个),可以提前将座位划分成小批次(比如每10个座位一组),给VenueSeat表添加batch_id字段标记批次。

// 开启事务
// 1. 先找到有可用座位的批次
$targetBatch = VenueSeat::available()
    ->where('event_id', 1)
    ->select('batch_id')
    ->groupBy('batch_id')
    ->havingRaw('COUNT(*) >= 2')
    ->first();

if (!$targetBatch) {
    DB::rollBack();
    return ['code' => 400, 'msg' => '剩余座位不足'];
}

// 2. 锁定该批次内的可用座位
$assignedSeats = VenueSeat::available()
    ->where('event_id', 1)
    ->where('batch_id', $targetBatch->batch_id)
    ->take(2)
    ->lockForUpdate()
    ->get();

// 3. 更新座位状态
VenueSeat::whereIn('id', $assignedSeats->pluck('id'))
    ->update(['status' => 'booked']);

// 提交事务

原理:将锁的范围从"所有可用座位"缩小到"单个批次的座位",即使高并发下,锁冲突的概率也会大幅降低,同时保证批次内的座位分配不会重复。


关于"先查ID再锁定"的顾虑

这种方式确实存在竞态风险:查询ID时未加锁,其他事务可能在你锁定前就抢走了这些座位。正确的做法是在查询座位的同时加锁(如方案1、2),确保查询到的座位从那一刻起就被当前事务独占,直到事务结束。

内容的提问来源于stack exchange,提问作者Alief

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 17:03:15