死鎖、悲觀鎖、樂觀鎖
Mysql 鎖型別和加鎖分析
MySQL有三種鎖的級別:頁級、表級、行級。
表級鎖:開銷小,加鎖快;不會出現死鎖;鎖定粒度大,發生鎖衝突的概率最高,併發度最低。
行級鎖:開銷大,加鎖慢;會出現死鎖;鎖定粒度最小,發生鎖衝突的概率最低,併發度也最高。
頁面鎖:開銷和加鎖時間界於表鎖和行鎖之間;會出現死鎖;鎖定粒度界於表鎖和行鎖之間,併發度
行級鎖與死鎖的產生過程
MyISAM中是不會產生死鎖的,因為MyISAM總是一次性獲得所需的全部鎖,要麼全部滿足,要麼全部等待。而在InnoDB中,鎖是逐步獲得的,就造成了死鎖的可能。
在MySQL中,行級鎖並不是直接鎖記錄,而是鎖索引。索引分為主鍵索引和非主鍵索引兩種,如果一條sql語句操作了主鍵索引,MySQL就會鎖定這條主鍵索引;如果一條語句操作了非主鍵索引,MySQL會先鎖定該非主鍵索引,再鎖定相關的主鍵索引。 在UPDATE、DELETE操作時,MySQL不僅鎖定WHERE條件掃描過的所有索引記錄,而且會鎖定相鄰的鍵值,即所謂的next-key locking。
當兩個事務同時執行,一個鎖住了主鍵索引,在等待其他相關索引。另一個鎖定了非主鍵索引,在等待主鍵索引。這樣就會發生死鎖。
死鎖
兩個或兩個以上的程序在執行過程中,因爭奪資源而造成的一種互相等待的現象,若無外力作用,它們都將無法推進下去。此時稱系統處於死鎖狀態或系統產生了死鎖,這些永遠在互相等待的程序稱為死鎖程序。由於資源佔用是互斥的,當某個程序提出申請資源後,使得有關程序在無外力協助下,永遠分配不到必需的資源而無法繼續執行,這就產生了一種特殊現象死鎖。 一種情形,此時執行程式中兩個或多個執行緒發生永久堵塞(等待),每個執行緒都在等待被其他執行緒佔用並堵塞了的資源。例如,如果執行緒A鎖住了記錄1並等待記錄2,而執行緒B鎖住了記錄2並等待記錄1,這樣兩個執行緒就發生了死鎖現象。
計算機系統中,如果系統的資源分配策略不當,更常見的可能是程式設計師寫的程式有錯誤等,則會導致程序因競爭資源不當而產生死鎖的現象。鎖有多種實現方式,比如意向鎖,共享-排他鎖,鎖表,樹形協議,時間戳協議等等。鎖還有多種粒度,比如可以在表上加鎖,也可以在記錄上加鎖。發生死鎖後,InnoDB一般都可以檢測到,並使一個事務釋放鎖回退,另一個獲取鎖完成事務。
產生死鎖的原因主要是
(1)系統資源不足。
(2)程序執行推進的順序不合適。
(3)資源分配不當等。
如果系統資源充足,程序的資源請求都能夠得到滿足,死鎖出現的可能性就很低,否則就會因爭奪有限的資源而陷入死鎖。其次,程序執行推進順序與速度不同,也可能產生死鎖。
產生死鎖的四個必要條件
(1) 互斥條件:一個資源每次只能被一個程序使用。
(2) 請求與保持條件:一個程序因請求資源而阻塞時,對已獲得的資源保持不放。
(3) 不剝奪條件:程序已獲得的資源,在末使用完之前,不能強行剝奪。
(4) 迴圈等待條件:若干程序之間形成一種頭尾相接的迴圈等待資源關係。
這四個條件是死鎖的必要條件,只要系統發生死鎖,這些條件必然成立,而只要上述條件之一不滿足,就不會發生死鎖。
如何避免死鎖
1.使用事務時,儘量縮短事務的邏輯處理過程,及早提交或回滾事務;
2.設定死鎖超時引數為合理範圍,如:3分鐘-10分種;超過時間,自動放棄本次操作,避免程序懸掛;
3.優化程式,檢查並避免死鎖現象出現;
4.對所有的指令碼和SP都要仔細測試,在正式版本之前;
5.所有的SP都要有錯誤處理(通過@error);
6.一般不要修改SQL SERVER事務的預設級別。不推薦強行加鎖。
7. 以固定的順序訪問表和行。
分為兩種情景:
對於不同事務訪問不同的表,儘量做到訪問表的順序一致;
對於不同事務訪問相同的表,儘量對記錄的id做好排序,執行順序一致;
8. 大事務拆小。大事務更傾向於死鎖,如果業務允許,將大事務拆小。
9. 在同一個事務中,儘可能做到一次鎖定所需要的所有資源,減少死鎖概率。
10. 降低隔離級別。如果業務允許,將隔離級別調低也是較好的選擇,比如將隔離級別從RR調整為RC,可以避免掉很多因為gap鎖造成的死鎖。
11. 為表新增合理的索引。可以看到如果不走索引將會為表的每一行記錄新增上鎖,死鎖的概率大大增大
如何處理死鎖
檢視正在執行的所有事務
SELECT * FROM INFORMATION_SCHEMA.INNODB_TRX\G;
檢視正在鎖的事務
SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCKS;
檢視等待鎖的事務
SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCK_WAITS;
1、最簡單的處理死鎖的方法就是重啟服務。
2、根據查詢對死鎖程序ID進行處理
根據第二步查詢到的死鎖進行,大致分析造成死鎖的原因,並通過如下語句釋放該死鎖程序
kill pid --pid為查詢出來的死鎖程序號
3、通過編寫儲存過程殺掉某個庫下面的所有死鎖程序和鎖
悲觀鎖
當我們使用悲觀鎖的時候我們首先必須關閉mysql資料庫的自動提交屬性,因為MySQL預設使用autocommit模式,也就是說,當你執行一個更新操作後,MySQL會立刻將結果進行提交。
關閉命令為:set autocommit=0;
悲觀鎖可以使用select…for update實現,在執行的時候會鎖定資料,雖然會鎖定資料,但是不影響其他事務的普通查詢使用。此處說普通查詢就是平時我們用的:select * from table 語句
使用悲觀鎖的時候事務中的語句例如:
//開始事務
begin;/begin work;/start transaction; (三選一)
//查詢資訊
select * from order where id=1 for update;
//修改資訊
update order set name=‘names’;
//提交事務
commit;/commit work;(二選一)
此處的查詢語句for update關鍵字,在事務中只有SELECT … FOR UPDATE 或LOCK IN SHARE MODE 同一條資料時會等待其它事務結束後才執行,一般的SELECT查詢則不受影響。
執行事務時關鍵字select…for update會鎖定資料,防止其他事務更改資料。但是鎖定資料也是有規則的。
查詢條件與鎖定範圍:
1、具體的主鍵值為查詢條件
比如查詢條件為主鍵ID=1等等,如果此條資料存在,則鎖定當前行資料,如果不存在,則不鎖定。
2、不具體的主鍵值為查詢條件
比如查詢條件為主鍵ID>1等等,此時會鎖定整張資料表。
3、查詢條件中無主鍵
會鎖定整張資料表。
4、如果查詢條件中使用了索引為查詢條件
明確指定索引並且查到,則鎖定整條資料。如果找不到指定索引資料,則不加鎖。
悲觀鎖的確保了資料的安全性,在資料被操作的時候鎖定資料不被訪問,但是這樣會帶來很大的效能問題。因此悲觀鎖在實際開發中使用是相對比較少的。
樂觀鎖
相對悲觀鎖而言,樂觀鎖假設資料一般情況下不會造成衝突,所以在資料進行提交更新的時候,才會對資料的衝突與否進行檢測,如果發現衝突,則讓返回使用者錯誤的資訊,讓使用者決定如何去做。
一般來說,實現樂觀鎖的方法是在資料表中增加一個version欄位,每當資料更新的時候這個欄位執行加1操作。這樣當資料更改的時候,另外一個事務訪問此條資料進行更改的話就會操作失敗,從而避免了併發操作錯誤。當然,還可以將version欄位改為時間戳,不過原理都是一樣的。
例如有表student,欄位:
id |
name |
version |
1 |
a |
1 |
當事務一進行更新操作:update student set name=‘ygz’where id = #{id} and version = #{version};
此時操作完後資料會變為id = 1,name = ygz,version = 2,當另外一個事務二同樣執行更新操作的時候,卻發現version != 1,此時事務二就會操作失敗,從而保證了資料的正確性。
悲觀鎖和樂觀鎖都是要根據具體業務來選擇使用
總結
悲觀鎖會鎖定資料,其他操作不會影響到被鎖的資料,但是普通的查詢沒有影響,需要用到 for update語句
實現樂觀鎖的方法是在資料表中增加一個version欄位,每當資料更新的時候這個欄位執行加1操作。這樣當資料更改的時候,另外一個事務訪問此條資料進行更改的話就會操作失敗,從而避免了併發操作錯誤。