乐观锁
乐观锁实现起来很容易,所谓的乐观锁就是我们认为不会存在并发同时修改一条数据,认为数据一般情况下不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果发现冲突了,则让返回用户错误的信息,让用户决定如何去做。
$ret = DB::table('order')->where('trade_no', $trade_no)->where('lock_version', $lock_version)->update([
'lock_version' => $lock_version+1
]);
// 影响行数小于1为更新失败
if ($ret<1) {
return false;
}
悲观锁
sharedLock
不会阻止其他 transaction 读取同一行。
lockForUpdate
会阻止其他 transaction 读取同一行。(需要特别注意的是,普通的非锁定读取读取依然可以读取到该行,只有 sharedLock 和 lockForUpdate 的读取会被阻止。)
使用 lockForUpdate 是有它的意义的,两个 transaction 要更新同一个计数器,如果不使用 lockForUpdate, 会导致两个 transaction 同时读到同一个初始值,然后在应用层逻辑中增加计数之后,提交到数据库中,后者的操作会覆盖掉前者的操作。
例子 demo1.php
DB::beginTransaction();
try {
$user = \App\Models\Users::sharedLock()->find(10000000);
echo $user->level."\n";
sleep(5);
$user->level += 1;
$user->save();
DB::commit();
} catch (Exception $e) {
DB::rollBack();
}
demo2
DB::beginTransaction();
try {
$user = \App\Models\Users::sharedLock()->find(10000000);
echo $user->level."\n";
sleep(5);
$user->level += 1;
$user->save();
DB::commit();
} catch (Exception $e) {
DB::rollBack();
}
当我们打开两个终端去分别运行以上两个方法时,会发现最后 level 只会加1,而不是加2,如果我们把 sharedLock 改成 lockForUpdate,则结果会加2。 以上的悲观锁只会针对事务 transaction 生效,所以我们处于外部也能正常读取到内容。
$user = \App\Models\Users::find(10000000);
$user-save();
能马上获取到数据,但是我们执行 $user-save();
时就会被以上阻塞,等待以上事务执行完毕后此处 save() 才会执行。
参考
https://laravel-china.org/topics/3208/note-pessimistic-lock-and-optimistic-lock