(資料庫十)資料庫中的鎖機制以及死鎖產生的原因及解決辦法

Nancy_G發表於2020-11-28

鎖的分類

在這裡插入圖片描述

根據鎖粒度分類

按照鎖的粒度,可以分為:行級鎖、表級鎖以及頁級鎖

1.行級鎖
(1) 描述
行級鎖是mysql中鎖定粒度最小的一種鎖。表示只針對當前操作的行進行加鎖。行級鎖能大大減少資料庫操作的衝突,其加鎖粒度最小,但加鎖的開銷也最大。行級鎖分為共享鎖和排他鎖。
(2)特點
開銷大,加鎖慢,會出現死鎖。發生鎖衝突的概率最低,併發度也最高。
2. 表級鎖
(1) 描述
表級鎖是mysql中鎖的粒度最大的一種鎖,表示對當前操作的整張表加鎖,它實現簡單,資源消耗較少,被大部分mysql引擎支援。最常使用的 MyISAM 與 InnoDB 都支援表級鎖定。表級鎖定分為表共享讀鎖(共享鎖)與表獨佔寫鎖(排他鎖)
(2)特點
開銷小,加鎖快,不會出現死鎖。發生鎖衝突的概率最高,併發度也最低。
3. 頁級鎖
(1) 描述
頁級鎖是 MySQL 中鎖粒度介於行級鎖和表級鎖中間的一種鎖。表級鎖速度快,但衝突多,行級衝突少,但速度慢。因此,採取了折中的頁級鎖,一次鎖定相鄰的一組記錄。BDB 支援頁級鎖。
(2)特點
開銷和加鎖時間界於表鎖和行鎖之間;會出現死鎖;鎖定粒度界於表鎖和行鎖之間,併發度一般。

行級鎖與死鎖

MyISAM中是不會產生死鎖的,因為MyISAM總是一次性獲得所需的全部鎖,要麼全部滿足,要麼全部等待。而在InnoDB中,鎖是逐步獲得的,就造成了死鎖的可能
在MySQL中,行級鎖並不是直接鎖記錄,而是鎖索引。索引分為主鍵索引和非主鍵索引兩種,如果一條sql語句操作了主鍵索引,MySQL就會鎖定這條主鍵索引;如果一條語句操作了非主鍵索引,MySQL會先鎖定該非主鍵索引,再鎖定相關的主鍵索引。 在UPDATE、DELETE操作時,MySQL不僅鎖定 WHERE 條件掃描過的所有索引記錄,而且會鎖定相鄰的鍵值,即所謂的 next-key locking。
當兩個事務同時執行,一個鎖住了主鍵索引,在等待其他相關索引。另一個鎖定了非主鍵索引,在等待主鍵索引。這樣就會發生死鎖。
發生死鎖後,InnoDB一般都可以檢測到,並使一個事務釋放鎖回退,另一個獲取鎖完成事務。

根據鎖級別分類

按照鎖級別可以分為:共享鎖、 排他鎖和意向鎖
1 共享鎖(Share Lock)
共享鎖又稱讀鎖,是讀取操作建立的鎖。其他使用者可以併發讀取資料,但任何事務都不能對資料進行修改。
如果事務T對資料A加上共享鎖後,則其他事務只能對A再加共享鎖,不能加排他鎖。獲取共享鎖的事務只能讀資料,不能修改資料。
用法

SELECT … LOCK IN SHARE MODE;

在查詢語句後面增加LOCK IN SHARE MODE,MySQL 就會對查詢結果中的每行都加共享鎖,當沒有其他執行緒對查詢結果集中的任何一行使用排他鎖時,可以成功申請共享鎖,否則會被阻塞。其他執行緒也可以讀取使用了共享鎖的表,而且這些執行緒讀取的是同一個版本的資料。
2 排他鎖(Exclusive Lock)
排他鎖又稱寫鎖、獨佔鎖,如果事務T對資料A加上排他鎖後,則其他事務不能再對A加任何型別的鎖。獲取排他鎖的事務既能讀資料,又能修改資料。
用法

SELECT … FOR UPDATE;

在查詢語句後面增加FOR UPDATE,MySQL 就會對查詢結果中的每行都加排他鎖,當沒有其他執行緒對查詢結果集中的任何一行使用排他鎖時,可以成功申請排他鎖,否則會被阻塞。
3 意向鎖(Intention Lock)
意向鎖是表級鎖,其設計目的主要是為了在一個事務中揭示下一行將要被請求鎖的型別。InnoDB 中的兩個表鎖:
意向共享鎖(IS):表示事務準備給資料行加入共享鎖,也就是說一個資料行加共享鎖前必須先取得該表的IS鎖;
意向排他鎖(IX):類似上面,表示事務準備給資料行加入排他鎖,說明事務在給一個資料行加排他鎖前必須先取得該表的IX鎖。
意向鎖是 InnoDB 自動加的,不需要使用者干預。
對於INSERT、UPDATE和DELETE,InnoDB 會自動給涉及的資料加排他鎖;對於一般的SELECT語句,InnoDB 不會加任何鎖,事務可以通過以下語句顯式加共享鎖或排他鎖。
共享鎖:SELECT … LOCK IN SHARE MODE;
排他鎖:SELECT … FOR UPDATE;

死鎖

死鎖產生的第一種情況

原因:事務之間對資源訪問順序的交替

一個使用者A 訪問表A(鎖住了表A),然後又訪問表B;另一個使用者B 訪問表B(鎖住了表B),然後企圖訪問表A;這時使用者A由於使用者B已經鎖住表B,它必須等待使用者B釋放表B才能繼續,同樣使用者B要等使用者A釋放表A才能繼續,這就死鎖就產生了。

解決方法

這種死鎖比較常見,是由於程式的BUG產生的,除了調整程式的邏輯沒有其它的辦法。仔細分析程式的邏輯,對於資料庫的多表操作時,儘量按照相同的順序進行處理,儘量避免同時鎖定兩個資源,如操作A和B兩張表時,總是按先A後B的順序處理, 必須同時鎖定兩個資源時,要保證在任何時刻都應該按照相同的順序來鎖定資源。

死鎖產生的第二種情況

原因:併發修改同一記錄

使用者A查詢一條紀錄,然後修改該條紀錄;這時使用者B修改該條紀錄,這時使用者A的事務裡鎖的性質由查詢的共享鎖企圖上升到獨佔鎖,而使用者B裡的獨佔鎖由於A 有共享鎖存在所以必須等A釋放掉共享鎖,而A由於B的獨佔鎖而無法上升的獨佔鎖也就不可能釋放共享鎖,於是出現了死鎖。這種死鎖比較隱蔽,但在稍大點的專案中經常發生。如在某專案中,頁面上的按鈕點選後,沒有使按鈕立刻失效,使得使用者會多次快速點選同一按鈕,這樣同一段程式碼對資料庫同一條記錄進行多次操作,很容易就出現這種死鎖的情況。
一般更新模式由一個事務組成,此事務讀取記錄,獲取資源(頁或行)的共享 (S) 鎖,然後修改行,此操作要求鎖轉換為排它 (X) 鎖。如果兩個事務獲得了資源上的共享模式鎖,然後試圖同時更新資料,則一個事務嘗試將鎖轉換為排它 (X) 鎖。共享模式到排它鎖的轉換必須等待一段時間,因為一個事務的排它鎖與其它事務的共享鎖不相容;發生鎖等待。第二個事務試圖獲取排它 (X) 鎖以進行更新。由於兩個事務都要轉換為排它 (X) 鎖,並且每個事務都等待另一個事務釋放共享鎖,因此發生死鎖。

解決方法

1、對於按鈕等控制元件,點選後使其立刻失效,不讓使用者重複點選,避免同時對同一條記錄操作。
2、使用樂觀鎖進行控制。樂觀鎖大多是基於資料版本(Version)記錄機制實現。即為資料增加一個版本標識,在基於資料庫表的版本解決方案中,一般是通過為資料庫表增加一個“version”欄位來實現。讀取出資料時,將此版本號一同讀出,之後更新時,對此版本號加一。此時,將提交資料的版本資料與資料庫表對應記錄的當前版本資訊進行比對,如果提交的資料版本號大於資料庫表當前版本號,則予以更新,否則認為是過期資料。樂觀鎖機制避免了長事務中的資料庫加鎖開銷(使用者A和使用者B操作過程中,都沒有對資料庫資料加鎖),大大提升了大併發量下的系統整體效能表現。Hibernate 在其資料訪問引擎中內建了樂觀鎖實現。需要注意的是,由於樂觀鎖機制是在我們的系統中實現,來自外部系統的使用者更新操作不受我們系統的控制,因此可能會造成髒資料被更新到資料庫中。
3、使用悲觀鎖進行控制。悲觀鎖大多數情況下依靠資料庫的鎖機制實現, 如Oracle的select … for update語句,以保證操作最大程度的獨佔性。但隨之而來的就是資料庫效能的大量開銷,特別是對長事務而言,這樣的開銷往往無法承受。如一個金融系統, 當某個操作員讀取使用者的資料,並在讀出的使用者資料的基礎上進行修改時(如更改使用者賬戶餘額),如果採用悲觀鎖機制,也就意味著整個操作過程中(從操作員讀 出資料、開始修改直至提交修改結果的全過程,甚至還包括操作員中途去煮咖啡的時間),資料庫記錄始終處於加鎖狀態,可以想見,如果面對成百上千個併發,這 樣的情況將導致災難性的後果。所以,採用悲觀鎖進行控制時一定要考慮清楚。
4、 SQL Server 可支援更新鎖
為解決死鎖,SQL Server 引入更新鎖,它有如下特徵:
(1) 加鎖的條件:當一個事務執行update語句時,資料庫系統會先為事務分配一把更新鎖。
(2) 解鎖的條件:當讀取資料完畢,執行更新操作時,會把更新鎖升級為獨佔鎖。
(3) 與其他鎖的相容性:更新鎖與共享鎖是相容的,也就是說,一個資源可以同時放置更新鎖和共享鎖,但是最多放置一把更新鎖。這樣,當多個事務更新相同的資料時,只有一個事務能獲得更新鎖,然後再把更新鎖升級為獨佔鎖,其他事務必須等到前一個事務結束後,才能獲取得更新鎖,這就避免了死鎖。
(4) 併發效能:允許多個事務同時讀鎖定的資源,但不允許其他事務修改它。

死鎖的第三種情況

原因:全表掃描

如果在事務中執行了一條不滿足條件的 update 語句,則執行全表掃描,把行級鎖上升為表級鎖,多個這樣的事務執行後,就很容易產生死鎖和阻塞。類似的情況還有當表中的資料量非常龐大而索引建的過少或不合適的時候,使得經常發生全表掃描,最終應用系統會越來越慢,最終發生阻塞或死鎖。

解決方法

SQL語句中不要使用太複雜的關聯多表的查詢;使用“執行計劃”對SQL語句進行分析,對於有全表掃描的SQL語句,建立相應的索引進行優化

相關文章