解決並發情況下庫存減為負數問題,庫存負數

來源:互聯網
上載者:User

解決並發情況下庫存減為負數問題,庫存負數

情境:

一個商品有庫存,下單時先檢查庫存,如果>0,把庫存-1然後下單,如果<=0,則不能下單,事務包含兩條sql語句:

select quantity from products WHERE id=3;update products set quantity = ($quantity-1) WHERE id=3;

在並發情況下,可能會把庫存減為負數(兩個進程同時select出來的都>0,然後都會執行update),怎麼辦呢?

方法1:

InnoDB支援通過特定的語句進行顯示加鎖:

select...lock in share mode

select...for udpate

select quantity from products WHERE id=3 for update;update products set quantity = ($quantity-1) WHERE id=3;

但是執行for update會產生一些其他的影響

1.select語句變慢

2.一些最佳化無法正常使用,例如索引覆蓋掃描

3.很容易造成伺服器的鎖爭用問題

方法二:

把udpate語句寫在前邊,先把數量-1,之後select出庫存如果>-1就commit,否則rollback。

update products set quantity = quantity-1 WHERE id=3;select quantity from products WHERE id=3 for update;

上邊的事務中先執行了update,所以id=3的行被加了行鎖,只有commit/rollback是才會被釋放(事務中鎖是逐步獲得的,但是都是commit時所釋放的)。很好的解決了並發問題。

方法三:

update語句在更新的同時加上一個條件

$quantity = select quantity from products WHERE id=3;update products set quantity = ($quantity-1) WHERE id=3 and queantity = $quantity;

這樣雖然select語句沒有加鎖,但是因為mysql的交易隔離等級是可重複讀,所以其他事務的修改不會影響到select的結果,當執行到update時,如果有其他事務鎖住了這條記錄,update會等待,等到其他事務釋放鎖,update會執行,但此時如果quantity的數量已經被修改,update的執行會返回影響行數為0。

原因:

情境比如先select出來的$quantity=3,然後執行update的條件為id = 3 and quantity=3,執行更新返回影響函數為0,但再次執行select時發現id=3的記錄quantity確實是3啊,怎麼有這條記錄卻更新不了呢?

這”歸功於“mysql的事物隔離等級和MVCC,當第一次select時$quantity=3,然後其他事務先於這個update執行了,導致update的條件並沒有找到合適的記錄,因為在可重複讀層級中,update的讀是“當前讀”,讀取的是最新的資料。而再次select時查到的$quantity還是等於3,因為對於select的讀是“快照讀”,讀取的是曆史資料,這也是可重複讀層級的特性。

詳細的Innodb中的交易隔離等級可以看美團點評技術團隊的一篇文章,有對於MVCC和間隙鎖的解釋:http://tech.meituan.com/innodb-lock.html

相關文章

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.