2018/06/27

laravel乐观锁和悲观锁的简单实现


乐观锁

乐观锁实现起来很容易,所谓的乐观锁就是我们认为不会存在并发同时修改一条数据,认为数据一般情况下不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果发现冲突了,则让返回用户错误的信息,让用户决定如何去做。

$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