場景
拼團功能,當 A 客戶開團之後(兩人團),如果 B 和 C 同時支付,如何規避兩人同時將拼團人數增加。
Laravel 中 sharedLock 與 lockForUpdate 的區別
sharedLock 對應的是 LOCK IN SHARE MODE
lockForUpdate 對應的是 FOR UPDATE
sharedLock 與 lockForUpdate 相同的地方是,都能避免同一行數據被其他 transaction 進行 update。
不同的地方是:
sharedLock 不會阻止其他 transaction 讀取同一行
lockForUpdate 會阻止其他 transaction 讀取同一行 (需要特別注意的是,普通的非鎖定讀取讀取依然可以讀取到該行,只有 sharedLock 和 lockForUpdate 的讀取會被阻止。)
即 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 裏,都會被鎖。
我依然有幾個疑問
Laravel 如何設置數據庫操作超時時間
什麼場景下適合使用 sharedLock 呢?
sharedLock,lockForUpdate 與 Pessimistic Locking 是什麼關係
Pessimistic locking(悲觀鎖) 與 Optimistic locking(樂觀鎖)的區別
如何測試 Laravel
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;
}
事務與鎖的關係
事務中涉及的操作都會加上鎖?
如果默認會加上鎖,那麼默認會加上什麼鎖呢?
事務中涉及的操作,不需要顯式加鎖?
要理清其中關係,就需要了解事務的四種隔離級別:
未提交讀(Read uncommitted)
已提交讀(Read committed)
可重複讀(Repeatable read)
可串行化(Serializable )
MySQL 默認的是:可重複讀(Repeatable read)