秒殺怎么樣才可以防止超賣?基於mysql的事務和鎖實現


Reference:  http://blog.ruaby.com/?p=256

 

並發事務處理帶來的問題?

相對於串行處理來說,並發事務處理能大大增加數據庫資源的利用率,提高數據庫系統的事務吞吐量,從而可以支持更多的用戶。但並發事務處理也會帶來一些問題,主要包括以下幾種情況:

  • 更新丟失(ost Update):當兩個或多個事務選擇同一行,然后基於最初選定的值更新該行時,由於每個事務都不知道其他事務的存在,就會發生丟失更新問題--最后的更新覆蓋了由其他事務所做的更新。例如,兩個編輯人員制作了同一文檔的電子副本。每個編輯人員獨立地更改其副本,然后保存更改后的副本,這樣就覆蓋了原始文檔。最后保存其更改副本的編輯人員覆蓋另一個編輯人員所做的更改。如果在一個編輯人員完成並提交事務之前,另一個編輯人員不能訪問同一文件,則可避免此問題。
  • 臟讀(Dirty Reads):一個事務正在對一條記錄做修改,在這個事務完成並提交前,這條記錄的數據就處於不一致狀態;這時,另一個事務也來讀取同一條記錄,如果不加控制,第二個事務讀取了這些“臟”數據,並據此做進一步的處理,就會產生未提交的數據依賴關系。這種現象被形象地叫做”臟讀”。
  • 不可重復讀(Non-Repeatabe Reads):一個事務在讀取某些數據后的某個時間,再次讀取以前讀過的數據,卻發現其讀出的數據已經發生了改變、或某些記錄已經被刪除了!這種現象就叫做“不可重復讀”。
  • 幻讀(Phantom Reads):一個事務按相同的查詢條件重新讀取以前檢索過的數據,卻發現其他事務插入了滿足其查詢條件的新數據,這種現象就稱為“幻讀”。

事務隔離級別

在上面講到的並發事務處理帶來的問題中,“更新丟失”通常是應該完全避免的。但防止更新丟失,並不能單靠數據庫事務控制器來解決,需要應用程序對要更新的數據加必要的鎖來解決,因此,防止更新丟失應該是應用的責任。

“臟讀”、“不可重復讀”和“幻讀”,其實都是數據庫讀一致性問題,必須由數據庫提供一定的事務隔離機制來解決。數據庫實現事務隔離的方式,基本上可分為以下兩種:

  • 一種是在讀取數據前,對其加鎖,阻止其他事務對數據進行修改。
  • 另一種是不用加任何鎖,通過一定機制生成一個數據請求時間點的一致性數據快照(Snapshot),並用這個快照來提供一定級別(語句級或事務級)的一致性讀取。從用戶的角度來看,好象是數據庫可以提供同一數據的多個版本,因此,這種技術叫做數據多版本並發控制(MutiVersion Concurrency Contro,簡稱MVCC或MCC),也經常稱為多版本數據庫。

數據庫的事務隔離越嚴格,並發副作用越小,但付出的代價也就越大,因為事務隔離實質上就是使事務在一定程度上 “串行化”進行,這顯然與“並發”是矛盾的。同時,不同的應用對讀一致性和事務隔離程度的要求也是不同的,比如許多應用對“不可重復讀”和“幻讀”並不敏感,可能更關心數據並發訪問的能力。

InnoDB的行鎖模式及加鎖方法

InnoDB實現了以下兩種類型的行鎖。

  • 共享鎖(S):允許一個事務去讀一行,阻止其他事務獲得相同數據集的排他鎖。
  • 排他鎖(X):允許獲得排他鎖的事務更新數據,阻止其他事務取得相同數據集的共享讀鎖和排他寫鎖。

對於UPDATE、DELETE和INSERT語句,InnoDB會自動給涉及數據集加排他鎖(X);對於普通SELECT語句,InnoDB不會加任何鎖;事務可以通過以下語句顯示給記錄集加共享鎖或排他鎖。

  • 共享鎖(S):SELECT * FROM table_name WHERE … LOCK IN SHARE MODE。
  • 排他鎖(X):SELECT * FROM table_name WHERE … FOR UPDATE。

用SELECT … IN SHARE MODE獲得共享鎖,主要用在需要數據依存關系時來確認某行記錄是否存在,並確保沒有人對這個記錄進行UPDATE或者DELETE操作。但是如果當前事務也需要對該記錄進行更新操作,則很有可能造成死鎖,對於鎖定行記錄后需要進行更新操作的應用,應該使用SELECT… FOR UPDATE方式獲得排他鎖。

更多詳情參考:http://www.cnblogs.com/qq78292959/archive/2013/01/30/2883100.html

github項目地址:https://github.com/sanjiOP/seckill

秒殺系統的實現

按照正常的購買流程:查詢商品庫存,庫存大於0時,生成訂單,去庫存。如果出現並發,導致在查詢商品庫存的時候,庫存會一直出現大於0的情況,出現超賣現象。

基於mysql的事務和鎖實現方式:

  • 1:開啟事務
  • 2:查詢庫存,並顯示的設置寫鎖(排他鎖):SELECT * FROM table_name WHERE … FOR UPDATE
  • 3:生成訂單
  • 4:去庫存,隱示的設置寫鎖(排他鎖):UPDATE goods SET counts = counts – 1 WHERE id = 1
  • 5:commit,釋放鎖

注意:

如果不開啟事務,第二步即使加鎖,第一個會話讀庫存結束后,變會釋放鎖,第二個會話仍有機會在去庫存前讀庫存,出現超賣。

如果開啟事務,第二步不加鎖,第一個會話讀庫存結束后,第二個會話容易出現【臟讀】,出現超賣。

即加事務,又加讀鎖:開啟事務,第一個會話讀庫存時加讀鎖,並發時,第二個會話也允許獲得讀庫存的讀鎖,但是在第一個會話執行寫操作時,寫鎖便會等待第二個會話的讀鎖,第二個會話執行寫操作時,寫鎖便會等待第一個會話的讀鎖,出現死鎖

即加事務,又加寫鎖:第一個會話讀庫存時加寫鎖,寫鎖會阻止其它事務的讀鎖和寫鎖。直到commit才會釋放,允許第二個會話查詢庫存,不會出現超賣現象。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM