一文帶你瞭解MySql併發事務中的數據庫鎖、隔離級別、MVCC

前言:

最近同事開發時遇到了一個事務阻塞的問題,經過網上查詢發現關於MySQL事務、鎖這一塊的資料都比較絮亂,讓人看的雲裏霧裏,因此藉着這個機會,恰好也對這一塊內容作一個總結梳理,但願能比較全面去寫一下MySQL的併發事務處理。mysql

本文主線:

  • 簡述事務的特性與隔離級別sql

  • 聊聊MySql中各類類型的鎖數據庫

  • 而後再聊聊MVCC是個什麼東東微信

  • 接着再聊聊數據庫鎖的觸發及升級,以及死鎖併發

  • 最後說下出現鎖問題時的常見排查命令性能

簡述事務的特性與隔離級別:

在講鎖以前,必需要先聊聊 事務的特性與隔離級別 ,由於鎖機制的存在是爲了保證事務對應隔離級別下的特性。學習

事務具備如下幾個特性: url

說完特性,再聊聊MySql中的幾種事務隔離級別:.net

RU 讀未提交:

顧名思義,在這種隔離級別下,當多個事務並行對同一數據進行操做時,會讀取未提交的數據,也被稱之爲 髒讀日誌

這種隔離級別由於會出現髒讀現象,因此在實際場景中不多用。

RC 讀提交:

一個事務只能看見已經提交事務所作的改變。

但這種隔離級別會出現 不可重複讀 現象,即在一個事務內,屢次讀同一數據,在這個事務尚未結束時,若是另外一個事務剛好修改了這個數據,那麼,在第一個事務中,兩次讀取的數據就可能不一致。

RR 可重複讀:

這是MySQL的 默認事務隔離級別 ,在這種隔離級別下,解決了RC存在的不可重複讀問題,確保在同一事務中,會看到一樣的數據行。

但可能會出現 幻讀 ,即當一個事務在執行讀取操做,第一次查詢數據總量後,另外一個事務執行了新增數據的操做並提交後,這個時候第一個事務讀取的數據總量和以前統計的不同,就像產生幻覺同樣。

SERIALIZABLE 串行化:

此隔離級別是四種隔離級別中最高的級別,解決了 髒讀、可重複讀、幻讀 的問題。

可是性能最差,它將事務的執行變爲順序執行,與其餘三個隔離級別相比,在並行事務執行過程當中,後一個事務的執行必須等待前一個事務結束。

MySql中各類類型的鎖:

在MySQL中,按鎖類型劃分,有如下種類:

提到鎖到種類,須要提一下MySQL到存儲引擎,MySQL經常使用引擎有 MyISAM和InnoDB ,而InnoDB是mysql默認的引擎。MyISAM是不支持行鎖的,而InnoDB支持行鎖和表鎖。

MyISAM 存儲引擎下表鎖:

MyISAM在執行查詢語句(SELECT)前,會自動給涉及的全部表加讀鎖,在執行更新操做(UPDATE、DELETE、INSERT等)前,會自動給涉及的表加寫鎖;

讀鎖會阻塞對同一張表的寫操做,而寫鎖既會阻塞對同一張表的寫操做,也會阻塞此表的讀操做。

排他鎖、共享鎖、意向鎖 是什麼東東?

排他鎖:

一般咱們在InnoDB存儲引擎中對錶執行一個更新操做,針對這一行數據會持有排他鎖;

持有排他鎖時,不容許再在數據行上添加寫鎖與讀鎖,其餘事務對此行數據的讀、寫操做都會被阻塞,只有當前事務提交了,鎖釋放了才容許其餘事務進行讀寫,達到避免 髒讀 的效果。

共享鎖:

主要是爲了支持併發的讀取數據而出現的,當一個事務持有某一數據行的共享鎖時,容許其它事務能夠再獲取共享鎖,但不容許其它事務在此基礎上獲取排他鎖;

也就是說,在持有共享鎖時,多個事務能夠同時讀取當前數據,可是不容許任何事務同時對當前數據進行修改操做,阻塞添加排它鎖。

意向鎖:

首先須要明白一點,意向鎖的做用是在 表上 的,當一個事務須要獲取共享鎖或排他鎖時,首先要獲取對應的意向鎖;

爲何要這樣作呢?舉個例子,假設在事務A中,對某一行數據添加共享鎖,這一行只能讀,不能寫;此時事務B申請得到表的寫鎖,假如加鎖成功,那麼事務B將可以對整個表的數據進行讀寫,與事務A衝突,這種操做確定是不容許的呢;

因此MySQL會在申請共享鎖或者排他鎖的時候,先獲取對應的意向鎖,也就是說,你要操做表中的某一行鎖數據,先要看看整個表能不能被操做;意向鎖的申請是由數據庫完成的,不須要人爲申請。

Innodb 存儲引擎下的行鎖:

上文對幾種鎖類型進行了簡要分析,其實平時工做開發中接觸到最多的仍是行鎖,行鎖的實現有如下幾種:

注意:在Innodb 存儲引擎中,行鎖的實現是基於索引的

Record Lock(記錄鎖):

它是會鎖住索引記錄,好比 update table where id = 1, id 是主鍵,而後在聚簇索引上對 id=1 的個索引記錄進行加鎖;

Gap Lock(間隙鎖):

實質上是對索引先後的間隙上鎖,不對索引自己上鎖,目的是爲了防止幻讀。

當使用範圍條件查詢而不是等值條件檢索數據,並請求排他鎖、或共享鎖時,對於該範圍內不存在的記錄,不容許其修改插入。

舉個例子:當表中只有一條id=101的記錄,一個事務執行select * from user where user_id > 100 for update;此時另外一個事務執行插入一條id=102的數據是會阻塞的,必須等待第一個事務提交後才能完成。

間隙鎖是針對事務隔離級別爲可重複讀或以上級別的。

Next-Key Lock:

Next-Key Lock 是 記錄鎖和間隙鎖 的結合,會同時鎖住記錄與間隙。

在innodb存儲引擎中,若是沒有經過 索引項 進行查詢時:

①、在RR隔離級別下,會以Next-Key Lock的方式對數據行進行加鎖,經過 行鎖+間隙鎖 實現了 "鎖表" 的效果,但請記住這不是添加的表鎖;

②、而在 RU、RC 隔離級別下仍是隻會鎖行記錄,爲何呢?由於在innodb存儲引擎下的四種事務隔離級別中都支持行鎖,可是間隙鎖只存在於RR、Serializable 兩種隔離級別下。

能夠經過下面這篇文章瞭解爲何在RR隔離級別下會實現"鎖表"的效果,而在RC隔離級別下只會鎖行記錄: 互聯網項目中mysql應該選什麼事務隔離級別

MVCC 是什麼:

鎖機制能夠控制併發操做,來保證一致性,可是系統開銷會很大;在RC、RR的隔離級別下,MySQL的InnoDB存儲引擎經過 MVCC (多版本併發控制) 機制來解決幻讀。

使用MVCC時具體的體現是什麼呢?

使事務在併發過程當中,SELECT 操做不用加鎖,讀寫不衝突從而提升性能。

那麼實現MVCC機制的原理是什麼呢?

其原理是經過保存數據在某個時間點的快照來實現的;經過在每行記錄後面保存隱藏列來存放事務ID,這樣每個事務,都會對應一個遞增的事務ID。

假設三個事務同時更新來同一行數據,那麼就會對應三個數據版本,但實際上版本一、版本2並非物理存在的,而是經過關聯記錄在undo log 回滾日誌中,這樣就能夠經過undo log找回數據的歷史版本,好比回滾的操做,會使用上一個版本的數據覆蓋數據頁上的數據。

舉例一個RR隔離級別下快照讀的例子:

開啓事務A按條件A查詢到兩條數據,此時事務B再插入1條數據且知足條件A的數據,並提交事務;

此時事務A再按條件A進行查詢,查詢到的依然是兩條數據,也就是說,事務A查詢到的並非當前最新的數據版本(三條數據),而是經過MVCC實現的歷史快照版本;這也是可重複讀的實現。

介紹了完讀操做,再舉例一個RR隔離級別下 更新 寫操做的例子:

注意:對數據修改的操做(update、insert、delete)都會讀到已提交事務的最新數據,由於這就是 當前讀。

假設事務A執行一個更新語句,知足更新條件A(條件A字段無索引或者存在非惟一索引)的數據是2條,更新成功後不提交事務;

此時事務B插入一條新的知足條件A的數據時會被阻塞的,爲何呢?

由於這裏在事務A更新時使用到了Next-Key Lock鎖,它會使用行鎖+間隙鎖實現了"鎖表",因此後面事務B再進行插入數據時會被阻塞的;這也防止了幻讀的出現。

這裏若是看的不是很明白的話,能夠同時再參考下此文章,此文章詳細分析了加鎖狀況:驚!史上最全的select加鎖分析(Mysql)

注意:

MVCC只在RC和RR兩個隔離級別下支持;其餘兩個隔離級別和MVCC不兼容,由於 RU老是讀取最新的數據行,而不是符合當前事務版本的數據行;而S ERIALIZABLE 則會對全部讀取的行都加鎖,是當前讀,也是讀取的最新數據。

數據庫鎖的觸發及升級,以及死鎖:

數據庫鎖的觸發及升級:

何時會出現DeadLock:

什麼是死鎖呢?

死鎖是指兩個或兩個以上的進程在執行過程當中,因爲競爭資源或者因爲彼此通訊而形成的一種阻塞的現象,若無外力做用,它們都將沒法推動下去。

此時稱系統處於死鎖狀態或系統產生了死鎖,這些永遠在互相等待的進程稱爲死鎖進程。

舉例說明:

事務A獲取 id=20的鎖,事務B獲取id=30的鎖,而後,事務A試圖獲取id=30的鎖,而該鎖已經被事務B持有,因此事務A等待事務 B釋放該鎖,而後事務B又試圖獲取id =20 的鎖這個鎖被事務 A 佔有,因而兩個事務之間相互等待,這就會致使死鎖。

死鎖的場景還有許多,歸根結底,都是由於多個事務想要獲取的鎖互斥且獲取的順序不一致所形成。

如何避免死鎖呢?

一般Record Lock引發的死鎖問題開發時都會比較當心,但Gap Lock可能致使死鎖的問題一般會被忽略,因此這一點要多加註意,另外就是創建合適的索引,若是沒有索引,那麼在操做數據時會鎖住每一行,會增大死鎖的機率。

出現鎖問題時的常見排查命令:

show open tabbles:

SHOW OPEN TABLES where In_use > 0:查看哪些表被鎖了

show status like 'table%':

  • table_locks_waited:出現表級鎖定爭用發生等待的次數,此值高說明存在驗證的表記鎖爭用狀況

  • table_locks_immediate:表示當即釋放表鎖的次數

show status like 'innodb_row_lock%':

  • Innodb_row_lock_current_waits:當前正在等待鎖定的數量

  • Innodb_row_lock_time:系統啓動到如今鎖定總時間

  • Innodb_row_lock_time_avg:每次等待話費的平均時間

  • Innodb_row_lock_time_max:系統啓動到如今等待最長一次所花時間

  • Innodb_row_lock_waits:系統啓動後到如今共等待次數

information_schema:

information_schema是MySQL專門記錄性能信息的庫,在5.7版本後默認打開。

  • SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCKS: 查看當前InnoDB的鎖的信息,會顯示是什麼鎖類型,屬於那個事務ID

  • SELECT * FROM INFORMATION_SCHEMA.INNODB_TRX: 查看InnoDB事務ID,會顯示是什麼操做和一些常規信息,例如是否在運行running,仍是等待鎖

  • SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCK_WAITS:查看InnoDB鎖的等待時間,和等待的是哪一個事務ID的鎖

♡ 點贊 + 評論 + 轉發 喲

若是本文對您有幫助的話,請揮動下您愛發財的小手點下贊呀,您的支持就是我不斷創做的動力,謝謝啦!

您能夠微信搜索 【木子雷】 公衆號,大量Java學習乾貨文章,您能夠來瞧一瞧喲!

相關文章
相關標籤/搜索