Laravel 用数据库事务与悲观锁守住并发写:transaction、lockForUpdate 与避免死锁
test12026-11-011 次阅读
并发写最怕两件事
余额扣减、库存扣减这类操作,多个请求同时进来,读到的旧值一样,最后一起写回,就会丢更新。解决靠两把锁:数据库事务保证原子,悲观锁(lockForUpdate)保证读的时候就堵住别人。
用 DB 门面写事务
use Illuminate\Support\Facades\DB;
DB::transaction(function () use ($userId, $amount) {
$wallet = DB::table('wallets')
->where('user_id', $userId)
->lockForUpdate()
->first();
if ($wallet->balance < $amount) {
throw new \RuntimeException('余额不足');
}
DB::table('wallets')
->where('user_id', $userId)
->update(['balance' => $wallet->balance - $amount]);
DB::table('transactions')->insert([
'user_id' => $userId,
'amount' => $amount,
'created_at' => now(),
]);
}, 3); // 第三个参数是死锁/失败时的重试次数
用 Eloquent 模型也一样
锁是查询作用域的一部分,模型查询链上直接加即可。
$user = User::findOrFail($id);
DB::transaction(function () use ($user, $amount) {
$wallet = $user->wallet()->lockForUpdate()->first();
$wallet->decrement('balance', $amount);
});
读锁、写锁与死锁
- lockForUpdate:写锁,别人不能读也不能写这行,直到事务提交。
- sharedLock:读锁,别人能读不能写,适合“先核对再决定”。
- 锁只在事务内生效,务必包在 DB::transaction 里,否则锁立刻释放。
- 多张表加锁时,所有请求按相同顺序加锁,避免 A 等 B、B 等 A 的死锁。
只有 InnoDB 等支持行锁的引擎才有效,MyISAM 会把整张表锁死。锁的范围越小越好,别在事务里塞慢查询,否则并发直接掉地上。
T
test1
文章作者
并发写最怕两件事 余额扣减、库存扣减这类操作,多个请求同时进来,读到的旧值一样,最后一起写回,就会丢更新。解决靠两把锁...
- 分类
- 技术
- 发布时间
- 2026-11-01
- 字数
- 约 1158 字
- 阅读
- 1 次
