1. 程式人生 > 資料庫 >MySQL資料庫鎖機制

MySQL資料庫鎖機制

鎖概念 : 當高併發訪問同一個資源時,可能會導致資料不一致,需要一種機制將使用者訪問資料的順序進行規範化,以保證資料庫資料的一致性。鎖就是其中的一種機制。


舉例 :以買火車票為例,火車票可面向廣大消費者,每位使用者都可以查詢餘票數量、購買火車票 ... 但最終購票成功的僅有一位使用者,處於購票高峰期時會出現很多使用者同時搶奪同一張票的現狀,為了避免出現一張火車票被多個使用者購買成功的情況,當第一位使用者進入購票流程時,就將資料庫鎖定,讓別的使用者無法修改,只有當第一位使用者購票成功或取消購票之後,才會解除資料庫的鎖定,此時別的使用者就可繼續進行操作。這樣就保證了一張火車票只能被一個使用者購買。

 

悲觀鎖: 一般代指資料庫鎖機制,類似於我們在多執行緒資源競爭時新增的互斥鎖,較容易出現死鎖現象。它對於資料被外界修改持保守態度,認為資料隨時會修改,所以整個資料處理中需要將資料加鎖。悲觀鎖一般都是依靠關係資料庫提供的鎖機制實現。

 

悲觀鎖按使用性質劃分:

資料庫的操作可歸納為兩種:讀和寫。當多個事務同時讀取一個物件時,不會產生有衝突。但同時讀和寫,或者同時寫會產生衝突。因此為提高資料庫的併發效能,定義三種鎖

共享鎖(Share locks簡記為S鎖):也稱讀鎖,事務A給物件T加S鎖,其他事務也只能對T加S,多個事務可以同時讀,但不能有寫操作,直到A釋放S鎖。

    地球語言 : 僅對資料進行讀操作,因此多個事務可以同時為一個物件新增共享鎖。(火車票人人都可隨時查詢)。

    產生共享鎖: select * from ad_plan lock in share mode;

排它鎖(Exclusivelocks簡記為X鎖):也稱寫鎖,事務A給物件T加X鎖以後,其他事務不能對T加任何鎖,只有事務A可以讀寫物件T,直到A釋放X鎖。

    地球語言: 對資料僅需寫/讀寫操作,只有一個事務可以為當前物件新增排他鎖,其餘事務不可再為其上鎖。(一個使用者已經進入購票流程,其餘客戶不能再購票)

    產生排他鎖: select * from ad_plan for update;

更新鎖(簡記為U鎖):用來預定要對此物件施加X鎖,它允許其他事務讀,但不允許再施加U鎖或X鎖;當被讀取的物件將要被更新時,則升級為X鎖,主要是用來防止死鎖的。因為使用共享鎖時,修改資料的操作分為兩步,首先獲得一個共享鎖,讀取資料,然後將共享鎖升級為排它鎖,然後再執行修改操作。這樣如果同時有兩個或多個事務同時對一個物件申請了共享鎖,在修改資料的時候,這些事務都要將共享鎖升級為排它鎖。這些事務都不會釋放共享鎖而是一直等待對方釋放,這樣就造成了死鎖。如果一個數據在修改前直接申請更新鎖,在資料修改的時候再升級為排它鎖,就可以避免死鎖。

 select * from information_schema.innodb_locks;     可以檢視鎖。 

 

悲觀鎖按作用範圍劃分:

行鎖:鎖的作用範圍是行級別,資料庫能夠確定那些行需要鎖的情況下使用行鎖,如果不知道會影響哪些行的時候就會使用表鎖。舉個栗子,現有一張學生表student,有主鍵id和學生名字name,假設現在需要使用 update ... where id=xxx 語句修改資料庫資料,因為主鍵欄位id在建立時已經預設建立了索引,所以資料庫能夠明確知道你需要修改的是哪一條記錄,此時僅會使用行鎖。但當使用 update ... where name=xxx 語句修改資料庫資料時,資料庫實現並不知道會影響哪些行,此時可能會使用表鎖。 

表鎖:表鎖的作用範圍是整張表。

 

資料庫死鎖:通常如果需要“修改”一條資料,資料庫管理系統會先在上面加鎖,以保證在同一時間只有一個事務能進行修改操作。鎖定(Locking)發生在一個事務獲取到某一資源的“鎖”時,其他的事務就不能更改這個資源了,這種機制的存在是為了保證資料一致性。多數情況下,可以認為如果一個資源被鎖定,它總會在以後某個時間被釋放。而死鎖發生在當多個程序訪問同一資料庫時,其中每個程序擁有的鎖都是其他程序所需的,由此造成每個程序都無法繼續下去。簡單的說,程序A等待程序B釋放他的資源,B又等待A釋放他的資源,這樣就互相等待就形成死鎖。

死鎖產生條件:

1)互斥條件:指程序對所分配到的資源進行排它性使用,即在一段時間內某資源只由一個程序佔用。如果此時還有其它程序請求資源,則請求者只能等待,直至佔有資源的程序用畢釋放。 2)請求和保持條件:指程序已經保持至少一個資源,但又提出了新的資源請求,而該資源已被其它程序佔有,此時請求程序阻塞,但又對自己已獲得的其它資源保持不放。 3)不剝奪條件:指程序已獲得的資源,在未使用完之前,不能被剝奪,只能在使用完時由自己釋放。 4)環路等待條件:指在發生死鎖時,必然存在一個程序——資源的環形鏈,即程序集合{P0,P1,P2,···,Pn}中的P0正在等待一個P1佔用的資源;P1正在等待P2佔用的資源,……,Pn正在等待已被P0佔用的資源。   一般情況只發生鎖超時,就是一個程序需要訪問資料庫表或者欄位的時候,另外一個程式正在執行帶鎖的訪問(比如修改資料),那麼這個程序就會等待,當等了很久鎖還沒有解除的話就會鎖超時,報告一個系統錯誤,拒絕執行相應的SQL操作。發生死鎖的情況比較少,比如一個程序需要訪問兩個資源(資料庫表或者欄位),當獲取一個資源的時候程序就對它執行鎖定,然後等待下一個資源空閒,這時候如果另外一個程序也需要兩個資源,而已經獲得並鎖定了第二個資源,那麼就會死鎖,因為當前程序鎖定第一個資源等待第二個資源,而另外一個程序鎖定了第二個資源等待第一個資源,兩個程序都永遠得不到滿足。   降低死鎖的發生:
  • 按同一順序訪問物件:如果所有併發事務按同一順序訪問物件,則發生死鎖的可能性會降低。例如,如果兩個併發事務獲得 Supplier 表上的鎖,然後獲得 Part 表上的鎖,則在其中一個事務完成之前,另一個事務被阻塞在 Supplier 表上。第一個事務提交或回滾後,第二個事務繼續進行。不發生死鎖。將儲存過程用於所有的資料修改可以標準化訪問物件的順序。
  • 避免事務中的使用者互動:避免編寫包含使用者互動的事務,因為執行沒有使用者互動的批處理的速度要遠遠快於使用者手動響應查詢的速度,例如答覆應用程式請求引數的提示。例如,如果事務正在等待使用者輸入,而使用者去吃午餐了或者甚至回家過週末了,則使用者將此事務掛起使之不能完成。這樣將降低系統的吞吐量,因為事務持有的任何鎖只有在事務提交或回滾時才會釋放。即使不出現死鎖的情況,訪問同一資源的其它事務也會被阻塞,等待該事務完成。
  • 保持事務簡短並在一個批處理中:在同一資料庫中併發執行多個需要長時間執行的事務時通常發生死鎖。事務執行時間越長,其持有排它鎖或更新鎖的時間也就越長,從而堵塞了其它活動並可能導致死鎖。保持事務在一個批處理中,可以最小化事務的網路通訊往返量,減少完成事務可能的延遲並釋放鎖。
  • 使用低隔離級別:確定事務是否能在更低的隔離級別上執行。執行提交讀允許事務讀取另一個事務已讀取(未修改)的資料,而不必等待第一個事務完成。使用較低的隔離級別(例如提交讀)而不使用較高的隔離級別(例如可序列讀)可以縮短持有共享鎖的時間,從而降低了鎖定爭奪。
  • 使用繫結連線:使用繫結連線使同一應用程式所開啟的兩個或多個連線可以相互合作。次級連線所獲得的任何鎖可以象由主連接獲得的鎖那樣持有,反之亦然,因此不會相互阻塞。

資料庫具體實現:

  • 使用事務時,儘量縮短事務的邏輯處理過程,及早提交或回滾事務;
  • 設定死鎖超時引數為合理範圍,如:3分鐘-10分種;超過時間,自動放棄本次操作,避免程序懸掛;
  • 所有的SP都要有錯誤處理(通過@error)
  • 一般不要修改SQL SERVER事務的預設級別。不推薦強行加鎖
  • 優化程式,檢查並避免死鎖現象出現;
  • 合理安排表訪問順序
  • 在事務中儘量避免使用者干預,儘量使一個事務處理的任務少些。
  • 採用髒讀技術。髒讀由於不對被訪問的表加鎖,而避免了鎖衝突。在客戶機/伺服器應用環境中,有些事務往往不允許讀髒資料,但在特定的條件下,我們可以用髒讀。
  • 資料訪問時域離散法。資料訪問時域離散法是指在客戶機/伺服器結構中,採取各種控制手段控制對資料庫或資料庫中的物件訪問時間段。主要通過以下方式實現: 合理安排後臺事務的執行時間,採用工作流對後臺事務進行統一管理。工作流在管理任務時,一方面限制同一類任務的執行緒數(往往限制為1個),防止資源過多佔用; 另一方面合理安排不同任務執行時序、時間,儘量避免多個後臺任務同時執行,另外,避免在前臺交易高峰時間執行後臺任務
  • 資料儲存空間離散法。資料儲存空間離散法是指採取各種手段,將邏輯上在一個表中的資料分散到若干離散的空間上去,以便改善對錶的訪問效能。主要通過以下方法實現: 第一,將大表按行或列分解為若干小表; 第二,按不同的使用者群分解。
  • 使用盡可能低的隔離性級別。隔離性級別是指為保證資料庫資料的完整性和一致性而使多使用者事務隔離的程度,SQL92定義了4種隔離性級別:未提交讀、提交讀、可重複讀和可序列。如果選擇過高的隔離性級別,如可序列,雖然系統可以因實現更好隔離性而更大程度上保證資料的完整性和一致性,但各事務間衝突而死鎖的機會大大增加,大大影響了系統性能。
  • 使用Bound Connections。Bound connections 允許兩個或多個事務連線共享事務和鎖,而且任何一個事務連線要申請鎖如同另外一個事務要申請鎖一樣,因此可以允許這些事務共享資料而不會有加鎖的衝突。
  • 考慮使用樂觀鎖定或使事務首先獲得一個獨佔鎖定。 

 

樂觀鎖:一般是指使用者自己實現的一種鎖機制,並不是真實存在的鎖。它對於資料被外界修改持樂觀態度,認為資料不會修改,所以資料處理時資料庫不再為其加鎖,但是在更新的時候會判斷一下在此期間別人有沒有去更新這個資料。可以在資料表中新增一個冗餘欄位,比如時間戳,在更新提交的時候檢查當前資料庫中資料的時間戳和自己更新前取到的時間戳進行對比,如果一致則OK,否則就是版本衝突。比如更新資料時,拿著之前相同的查詢條件再一次查詢資料庫,若仍能夠得到資料證明此條記錄無人修改,即可繼續操作,否則表示當前有使用者正在搶奪資源,就放棄更新操作。

 

樂觀鎖實現方式:

a. 版本號(記為version):在表中新增一個version欄位,作為版本標識的記號,當資料每次更新時就將此欄位加1,每次讀取資料時一併將version欄位讀出,更新資料之前比較version欄位值。舉個栗子,若此次讀取的 新version值 比 舊version值 大,說明有其他事務在此之前修改過這條記錄,併為版本號欄位增加了數量,此時就無法得到這條記錄,需要重新開始一遍。此欄位存在的意義是作為一個標誌位,準備修改資料時將version讀出,真正修改資料前再查詢一次version,比較上一次得到的version值和現在version是否一致,相同繼續操作,不同重新開始。可使用類似 update … where … and version=”old version” 語句進行比較。根據返回結果是否為0執行下一步的操作。

b. 時間戳(timestamp):和版本號基本一樣,只是通過時間戳來判斷而已,注意時間戳要使用資料庫伺服器的時間戳,而不能是業務系統的時間。

c. 待更新欄位:和版本號方式相似,只是不增加額外欄位,直接使用表中現有做版本控制資訊的標誌位,因為有時我們可能無法改變舊系統的資料庫表結構。假設現在需要儲存一個訂單記錄,有庫存stock欄位: 首先需要查詢資料庫,得到這個商品的庫存數量,再判斷庫存數量是否大於使用者購買數量,經歷一系列判斷邏輯都能夠通過的話,儲存這個訂單資料之前,需要拿著當初查詢資料庫時的庫存欄位再查詢一次這個商品,若通過原始庫存值能夠得到商品物件,那麼就進行訂單的修改操作,否則就是別的使用者正在搶奪資源,應放棄操作重新再來。    

d. 所有欄位:和待更新欄位類似,只是使用所有欄位做版本控制資訊,只有所有欄位都沒有變化才會執行更新。

 

 

鎖的級別:頁級、表級、行級。
  MySQL的鎖機制比較簡單,最顯著的特點是不同的儲存引擎支援不同的鎖機制。
   MyISAM & MEMORY: 表級鎖(table-level locking),  BDB: 頁面鎖(page-level locking)&表級鎖,  InnoDB:  行級鎖(row-level locking)&表級鎖,預設採用行級鎖。

3種鎖特性:
表級鎖:開銷小,加鎖快;不會出現死鎖;鎖定粒度大,發生鎖衝突的概率最高,併發度最低。對整個表加鎖,影響標準的所有記錄。通常用在DDL語句中,如DELETE TABLE,ALTER TABLE等。  
行級鎖:開銷大,加鎖慢;會出現死鎖;鎖定粒度最小,發生鎖衝突的概率最低,併發度也最高。對一行記錄加鎖,隻影響一條記錄。通常用在DML語句中,如INSERT, UPDATE, DELETE等。
頁面鎖:開銷和加鎖時間界於表鎖和行鎖之間;會出現死鎖;鎖定粒度界於表鎖和行鎖之間,併發度一般。
資料庫引擎通常必須獲取多級別上的鎖才能完整地保護資源。


鎖的應用場景:

樂觀鎖適用於高併發、讀多寫少的場景,發生衝突時能夠減少開銷.

如果需要非常高的響應速度,建議採用樂觀鎖方案,成功就執行,不成功就失敗,不需要等待其他併發去釋放鎖

如果衝突頻率非常高,建議採用悲觀鎖保證成功率,如果衝突頻率大,樂觀鎖會需要多次重試才能成功,代價比較大

如果重試代價大,建議採用悲觀鎖

 

 

鎖的優缺點:

樂觀鎖不會發生死鎖情況,不會過多佔用系統資源,無法阻止除資料庫之外的操作

悲觀鎖寫入資料時能夠確保資料的準確性