1. 程式人生 > 其它 >死鎖、悲觀鎖、樂觀鎖

死鎖、悲觀鎖、樂觀鎖

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=ygzwhere id = #{id} and version = #{version};

此時操作完後資料會變為id = 1,name = ygz,version = 2,當另外一個事務二同樣執行更新操作的時候,卻發現version != 1,此時事務二就會操作失敗,從而保證了資料的正確性。

悲觀鎖和樂觀鎖都是要根據具體業務來選擇使用

總結

悲觀鎖會鎖定資料,其他操作不會影響到被鎖的資料,但是普通的查詢沒有影響,需要用到 for update語句

實現樂觀鎖的方法是在資料表中增加一個version欄位,每當資料更新的時候這個欄位執行加1操作。這樣當資料更改的時候,另外一個事務訪問此條資料進行更改的話就會操作失敗,從而避免了併發操作錯誤。