拼团功能,当 A 客户开团之后(两人团),如果 B 和 C 同时支付,如何规避两人同时将拼团人数增加。
sharedLock 与 lockForUpdate 相同的地方是,都能避免同一行数据被其他 transaction 进行 update。
不同的地方是:
即 sharedLock locks only for write, lockForUpdate also prevents them from being selected
这样做是有意义的,例如,两个 transaction 要更新同一个计数器,如果不使用 lockForUpdate, 会导致两个 transaction 同时读到同一个初始值,然后在应用层逻辑中增加计数之后,提交到数据库中,后者的操作会覆盖掉前者的操作。
在 MySQL 命令行终端操作一个表
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from users for update;
+----+------+
| id | name |
+----+------+
| 1 | tom |
| 2 | bob |
+----+------+
这时再开一个命令行终端
mysql> select * from users for update;
^C^C -- query aborted
ERROR 1317 (70100): Query execution was interrupted
mysql> select * from users lock in share mode;
^C^C -- query aborted
ERROR 1317 (70100): Query execution was interrupted
你会发现,无论是 for update 还是 lock in share mode 都无法读取到数据,更加确切地说是,查询被阻塞了。
只有在第一个终端执行
commit;
第二个终端才能得到数据返回。
需要注意的是,发起者必须在 transaction 里上锁才有效,如果不是在 transaction 中,上锁是无效的。但是,第二个人无论是不是在 transaction 里,都会被锁。
A 用户,在浏览器里访问接口 (模拟支付回调),此时对数据表中某一行锁住,进行 30s 操作,然后提交事务。
B 用户,在浏览器里访问同一接口 (模拟支付回调),其无法修改该行。对应的返回是什么?
会一直 wait 到数据库操作超时。
那么问题来了,Laravel 如何设置数据库操作超时时间?
简单的测试方法,是在命令行中开两个 artisan tinker 窗口,分别执行
DB::transaction(function () {
echo 1;
User::where('id', 33)->lockForUpdate()->get();
echo 2;
sleep(10);
});
你会发现第二个 tinker 窗口中的 get 操作,需要等到第一个 transaction 执行完毕之后,才能得到查询结果。
需要注意的是,不在 transaction 中的 lockForUpdate 操作,是没有锁效果的。
真实场景,防止用户重复提现
DB::transaction(function () use ($user, &$user_award) {
$user_award = UserAward::where([
['user_id', $user->id],
['status', 0],
])
->lockForUpdate()
->first();
if ($user_award) {
$user_award->status = 1; // 提现中状态
$user_award->save();
}
});
if (!is_null($user_award)) {
$amount = $user_award->money * 100;
}
事务中涉及的操作都会加上锁?
如果默认会加上锁,那么默认会加上什么锁呢?
事务中涉及的操作,不需要显式加锁?
要理清其中关系,就需要了解事务的四种隔离级别:
MySQL 默认的是:可重复读(Repeatable read)
FOR UPDATE
and LOCK IN SHARE MODE
- Stack Overflow转载地址:https://www.sunzhongwei.com/using-laravel-sharedlock-and-lockforupdate-for-table-row-locks