假設有一張表,儲存一個iPhone商品跟剩餘庫存量。
假設A/B兩個人同時間進來買商品,如果沒有任何防護機制,資料庫中的商品剩餘庫存量有可能會記錄錯誤,比如iPhone庫存剩10個,此時兩個人"同時"進來搶,由於是同時,所以select到的in memory庫存量都會是10,A/B都會把庫存量-1(10-1)再update資料表,這種情況下剩餘庫存量會是9而不是8,就會發生記錄錯誤的問題。
如果商品庫存剩1個,明明是A先買到,這時候就已經沒庫存了,可是系統卻告訴B恭喜你搶到了,這種情況就更糟糕了,只剩下一支iPhone,卻跟兩個人(甚至更多人)都說有買到,也就是發生所謂的超賣問題。
所以資料庫必須有一些機制來保證資料的正確性,來避免上述所說的問題。
悲觀鎖 Pessimistic Lock - 使用資料庫 Transaction 機制來強迫執行順序,確保每次都只有一個事務執行。
每次下SQL時都確保同時只會有一個事務執行,也就是等A SELECT出庫存量並且-1 UPDATE庫存量之後,才釋放出Lock換B執行,這時候B SELECT拿到的庫存數量就會是9,9-1=8再去UPDATE庫存量,也就可以解決數據不正確的問題。
模擬悲觀鎖:
mysql> begin;
mysql> select stock from `product` where id = 1 for update;
mysql> update `product` set stock = stock -1 where id = 1;
mysql> commit;
可開啟兩個mysql console視窗模擬,A視窗下了for update 會馬上跑出SELECT結果,這時候B視窗也下了這行,會發現沒有馬上執行,必須等待A視窗commit後釋放出lock,B視窗才會繼續執行,等到commit後才又把lock釋放出來。
- 注意:上述需要支援 Transaction,本文是用InnoDB儲存引擎,MyISAM 是不支援 Transaction 的,如果是用MyISAM會發現for update根本就不會lock住。
- 悲觀鎖適合寫多讀少的情境。
- 這種方法的缺點是,如果同時出現大量的讀取操作,由於Lock機制,會需要等待時間,因此可能會有效能問題。