MySQL中的select for update大家應該都有所接觸,但什麼時候該去使用,以及有哪些需要註意的地方會有很多不清楚的地方,我把我如何使用和查詢到的文檔在此記錄。 作用 select本身是一個查詢語句,查詢語句是不會產生衝突的一種行為,一般情況下是沒有鎖的,用select for upda ...
MySQL中的select for update大家應該都有所接觸,但什麼時候該去使用,以及有哪些需要註意的地方會有很多不清楚的地方,我把我如何使用和查詢到的文檔在此記錄。
作用
select本身是一個查詢語句,查詢語句是不會產生衝突的一種行為,一般情況下是沒有鎖的,用select for update 會讓select語句產生一個排它鎖(X), 這個鎖和update的效果一樣,會使兩個事務無法同時更新一條記錄。
什麼時候使用
我認為的錯誤的用法
在網上看到一篇文章, 裡面講到用select for update來產生一個悲觀鎖,保證庫存的一致性,其他update在更新的時候本身就有悲觀鎖,不會讓另外一個事務更新,如果按照他的寫法update goods set stock = stock - 1 where id = 1;
是沒必要先去查詢的
我認為的正確的用法(1)
有時我們確實需要先將數據查詢出來,然後再去更新,但我們不想的是,我剛查詢完結果沒還更新,這時被其他事務更新了,例如上面的例子,如果我想先查詢庫存,確認庫存是否足夠,如果足夠就去更新,不夠就返回一個錯誤
begin;
select * from goods where id = 1;
// 檢查庫存足夠代碼
update goods set stock = stock - 1 where id = 1;
commit;
如果不用for update的情況下,在檢查庫存時,被其他事務更新了庫存,就會產生庫存不足,但是當前事務以為充足的情況發生, 所以使用for update先鎖住這條記錄,其他事務不能去更改這條記錄直到當前事務提交
我認為的正確的用法(2)
在我們實際業務中,一個訂單要扣除多個補劑的庫存,這時要用事務保證一個訂單多個補劑同時扣除成功或者同時失敗,可能會出現這樣的情況,M訂單需要補劑a,b,c三種補劑,N訂單需要c,e,a三種補劑,這時兩個事務同時執行,M事務扣除ab庫存,N事務庫存c,e庫存,因為update會產生排它鎖阻止其他事務更新當前事務已經更新的記錄,所以這會產生死鎖,M在等待獲取c的鎖,N在等待獲取a的鎖,在訂單比較多的情況下,這種衝突很容易出現,我的解決方法是當M需要a,b,c三種補劑時,使用select for update鎖定三條記錄,此時N訂單select for update獲取不到c的鎖直到M訂單事務提交
2022.03.29 Update: 經過很長一段時間工作之後,我發現我認為的正確用法(2)是錯誤的,這個和select for update沒有關係,關鍵是要對共用資源進行排序,例如:有a-z共26中補劑,M訂單需要a、d、e,N訂單需要d、h、z,這這種情況中,誰先拿到衝突的資源誰會先執行完,因為衝突的資源後面的資源肯定不是衝突的,也就是說只使用事務+多個update也可以實現
至於其他的用法可以我後面遇到會再總結進去
註意事項
- select for update會根據where條件來鎖多條記錄, 根據where條件的欄位是不是索引來決定鎖定表還是鎖定行,同時根據是聚簇索引還是二級索引和查詢條件(範圍查詢還是精確查詢)來決定鎖定特定的行還是一個範圍內的行
- 當鎖定範圍的行時會有gap lock和next-key來防止插入新的行參考StackOverFlow
SELECT * FROM information_schema.innodb_trx \G
使用該語句可以查詢某個事務鎖了幾個表,鎖了幾個行
參考
[1] MySQL InnoDB存儲引擎(三):鎖及事務模型