一、mysql的鎖類型
(1) 共享/排它鎖(Shared and Exclusive Locks)
共享鎖和排他鎖是InnoDB引擎實現的標準行級別鎖。
拿共享鎖是爲了讓當前事務去讀一行數據。
拿排他鎖是爲了讓當前事務去修改或刪除某一行數據。。
設置共享鎖:select * from user where id = 1 LOCK IN SHARE MODE;
設置排他鎖:select * from user where id = 1 FOR UPDATE;
(2) 意向鎖(Intention Locks)
意向鎖存在的意義在於,使得行鎖和表鎖能夠共存。
意向鎖是表級別的鎖,用來說明事務稍後會對錶中的數據行加哪種類型的鎖(共享鎖或獨佔鎖)。
當一個事務對錶加了意向排他鎖時,另外一個事務在加鎖前就會通過該表的意向排他鎖知道前面已經有事務在對該表進行獨佔操作,從而等待。
(3) 記錄鎖(Record Locks)
記錄鎖是索引記錄上的鎖,例如:SELECT c1 FROM t WHERE c1 = 10 FOR UPDATE;會阻止其他事務對c1=10的數據行進行插入、更新、刪除等操作。
記錄鎖總是鎖定索引記錄。如果一個表沒有定義索引,那麼就會去鎖定隱式的“聚集索引”。
(4) 間隙鎖(Gap Locks)
間隙鎖是一個在索引記錄之間的間隙上的鎖。
一個間隙可能跨越單個索引值、多個索引值,甚至爲空。
對於使用唯一索引 來搜索唯一行的語句,只加記錄鎖不加間隙鎖(這並不包括組合唯一索引)。
(5) 臨鍵鎖(Next-key Locks)
Next-Key Locks是行鎖與間隙鎖的組合。當InnoDB掃描索引記錄的時候,會首先對選中的索引記錄加上記錄鎖(Record Lock),然後再對索引記錄兩邊的間隙加上間隙鎖(Gap Lock)。
(6) 插入意向鎖(Insert Intention Locks)
插入意向鎖是在數據行插入之前通過插入操作設置的間隙鎖定類型。
如果多個事務插入到相同的索引間隙中,如果它們不在間隙中的相同位置插入,則無需等待其他事務。例如:在4和7的索引間隙之間兩個事務分別插入5和6,則兩個事務不會發衝突阻塞。
(7) 自增鎖(Auto-inc Locks)
自增鎖是事務插入到有自增列的表中而獲得的一種特殊的表級鎖。如果一個事務正在向表中插入值,那麼任何其他事務都必須等待,保證第一個事務插入的行是連續的自增值。
二、鎖的實現方式
InnoDB行鎖是通過給索引加鎖來實現的,如果沒有索引,InnoDB會通過隱藏的聚簇索引來對記錄進行加鎖(全表掃描,也就是表鎖)。
但是,爲了效率考量,MySQL做了優化,對於不滿足條件的記錄,會放鎖,最終持有的,是滿足條件的記錄上的鎖。但是不滿足條件的記錄上的加鎖/放鎖動作是不會省略的。所以在沒有索引時,不滿足條件的數據行會有加鎖又放鎖的耗時過程。
索引分爲主鍵索引和非主鍵索引兩種。如果一條sql語句操作了主鍵索引,MySQL就會鎖定對應主鍵索引;如果一條語句操作了非主鍵索引,MySQL會先鎖定非主鍵索引,再鎖定對應的主鍵索引。
三、mysql鎖在4種事務隔離級別裏的應用
事務的四種隔離級別有:
-
讀未提交(Read Uncommitted)
<p style="margin-left:0px;"><span style="color:rgba(0,0,0,.75);"><span style="color:#4f4f4f;">此時select語句不加任何鎖。此時併發最高,但會產生髒讀。</span></span></p> </li> <li> <p style="margin-left:0px;"><span style="color:rgba(0,0,0,.75);"><span style="color:#4f4f4f;"> <strong>讀提交</strong>(Read Committed, RC)</span></span></p> <p style="margin-left:0px;"><span style="color:rgba(0,0,0,.75);"><span style="color:#4f4f4f;"><span style="color:#008000;">普通select語句</span>是<span style="color:#008000;"><strong>快照讀</strong></span></span></span></p> <p style="margin-left:0px;"><span style="color:rgba(0,0,0,.75);"><span style="color:#4f4f4f;"><span style="color:#008000;">update</span>語句、<span style="color:#008000;">delete</span>語句、<span style="color:#008000;">顯示加鎖的select語句(select … in share mode 或者 select … for update)</span> 等,除了在<strong>外鍵約束檢查</strong>和<strong>重複鍵檢查</strong>時會封鎖區間,其他情況都只使用<span style="color:#008000;"><strong>記錄鎖</strong></span>;</span></span></p> </li> <li> <p style="margin-left:0px;"><span style="color:rgba(0,0,0,.75);"><span style="color:#4f4f4f;"> <strong>可重複讀</strong>(Repeated Read, RR)</span></span></p> <p style="margin-left:0px;"><span style="color:rgba(0,0,0,.75);"><span style="color:#4f4f4f;"><span style="color:#008000;">普通select語句</span>也是<span style="color:#008000;"><strong>快照讀</strong></span></span></span></p> <p style="margin-left:0px;"><span style="color:rgba(0,0,0,.75);"><span style="color:#4f4f4f;"><span style="color:#008000;">update</span>語句、<span style="color:#008000;">delete</span>語句、<span style="color:#008000;">顯示加鎖的select語句(select … in share mode 或者 select … for update)</span>則要分情況:</span></span></p> <ul style="margin-left:0px;"><li> <p style="margin-left:0px;"><span style="color:rgba(0,0,0,.75);"><span style="color:#4f4f4f;">在唯一索引上使用唯一的查詢條件,則使用<span style="color:#008000;"><strong>記錄鎖</strong></span>。如: select * from user where id = 1;其中id建立了唯一索引。</span></span></p> </li> <li> <p style="margin-left:0px;"><span style="color:rgba(0,0,0,.75);"><span style="color:#4f4f4f;">在唯一索引上使用 範圍查詢條件,則使用<span style="color:#008000;"><strong>間隙鎖與臨鍵鎖</strong></span>。如: select * from user where id >20;</span></span></p> </li> </ul></li> <li> <p style="margin-left:0px;"><span style="color:rgba(0,0,0,.75);"><span style="color:#4f4f4f;"> <strong>串行化</strong>(Serializable)</span></span></p> <p style="margin-left:0px;"><span style="color:rgba(0,0,0,.75);"><span style="color:#4f4f4f;">此時所有select語句都會被<strong>隱式加鎖</strong>:select … in share mode.</span></span></p> </li>
四、快照讀、當前讀
要理解前面四種隔離級別的加鎖方式,對於MVCC、快照讀、當前讀 都是必須要理解的。
MVCC併發控制中,讀操作可以分成兩類:快照讀 (snapshot read)與當前讀 (current read)。
快照讀,讀取的是記錄的可見版本 (有可能是歷史版本),不用加鎖。
當前讀,讀取的是記錄的最新版本,並且,當前讀返回的記錄,都會加上鎖,保證其他事務不會再併發修改這條記錄。
什麼是多版本併發控制(MVCC:multi-version concurrency control )
-
MVCC定義:多版併發控制系統。可認爲是行級鎖的一個變種,它能夠避免更多情況下的加鎖操作。
-
作用:避免一些加鎖操作,提升併發性能。
-
實現:通過在每行記錄的後面保存行的創建時間和過期時間或刪除時間(它們是隱藏的),這兩個時間實際都是系統的版本號。每開始一個新的事務,版本號都會自動增加。
-
具體原理
4.1) select:innoBD查詢時會檢查以下兩個條件:一個是數據行的版本號早於當前事務的版本號;另一個是行的刪除版本號,要麼沒有,要麼大於當前事務的版本號。<p style="margin-left:0px;"><span style="color:rgba(0,0,0,.75);"><span style="color:#4f4f4f;">4.2)insert/delete:innoDB將當前的系統版本號作爲新插入(刪除)的數據行的版本號。</span></span></p> <p style="margin-left:0px;"><span style="color:rgba(0,0,0,.75);"><span style="color:#4f4f4f;">4.3)update:先新插入一行數據,並將當前系統版本號作爲行的版本號,同時將當前系統版本號作爲原來行的刪除版本號。更新主鍵時,聚集索引和普通索引都會產生兩個版本;而更新非主鍵時,只要普通索引會產生兩個版本。</span></span></p> </li> <li style="margin-left:40px;"> <p style="margin-left:0px;"><span style="color:rgba(0,0,0,.75);"><span style="color:#4f4f4f;"><strong>注意</strong>:<span style="color:#ff0000;">MVCC只在read committed和repeatable read兩個隔離級別下工作</span>。<br> [參考:《高性能mysql》]</span></span></p> </li>
快 照 讀 是 哪 些
一個正常的select…語句就是快照讀。
快照讀,使得在RR(repeatable read)級別下一個普通select...語句也能做到可重複讀。即前面MVCC裏提到的利用可見版本來保證數據的一致性。
當 前 讀 是 哪 些
insert語句、update語句、delete語句、顯示加鎖的select語句(select… LOCK IN SHARE MODE、select… FOR UPDATE)是當前讀。
爲什麼insert、update、delete語句都屬於當前讀?
這是因爲這些語句在執行時,都會執行一個讀取當前數據最新版本的過程。
當前讀的SQL語句,InnoDB是逐條與MySQL Server交互的。即先對一條滿足條件的記錄加鎖後,再返回給MySQL Server,當MySQL Server做完DML操作後,再對下一條數據加鎖並處理。
五、查看行級鎖爭用情況
執行SQL:mysql> show status like 'InnoDB_row_lock%';
- mysql> show status like 'InnoDB_row_lock%';
- +-------------------------------+-------+
- | Variable_name | Value |
- +-------------------------------+-------+
- | InnoDB_row_lock_current_waits | 0 |
- | InnoDB_row_lock_time | 0 |
- | InnoDB_row_lock_time_avg | 0 |
- | InnoDB_row_lock_time_max | 0 |
- | InnoDB_row_lock_waits | 0 |
- +-------------------------------+-------+
如果發現鎖爭用比較嚴重,還可以通過設置InnoDB Monitors 來進一步觀察發生鎖衝突的表、數據行等,並分析鎖爭用的原因。如:
設置監視器:mysql> create table InnoDB_monitor(a INT) engine=InnoDB;
查看:mysql> show engine InnoDB status;
停止查看:mysql> drop table InnoDB_monitor;
具體參考:InnoDB Monitor
六、死鎖
什麼是死鎖:一般是由於兩個事務同時操作兩個表,但加鎖的順序是不一致出現的。比如A事務先鎖a表,B事務先鎖b表;當A去鎖b表的時候發現b表被B事務鎖住了,要等待;當B事務去鎖a表的時候發現a表被A鎖住了。於是出現了死鎖
如何發現死鎖: 在InnoDB的事務管理和鎖定機制中,有專門檢測死鎖的機制,會在系統中產生死鎖之後的很短時間內就檢測到該死鎖的存在,一般像一些運維繫統是能發現的
解決辦法:回滾較小的那個事務
在REPEATABLE-READ隔離級別下,如果兩個線程同時對相同條件記錄用SELECT…FOR UPDATE加排他鎖,在沒有符合該條件記錄情況下,兩個線程都會加鎖成功。程序發現記錄尚不存在,就試圖插入一條新記錄,如果兩個線程都這麼做,就會出現死鎖。這種情況下,將隔離級別改成READ COMMITTED,就可避免問題。
如何判斷事務大小:事務各自插入、更新或者刪除的數據量
注意:
當產生死鎖的場景中涉及到不止InnoDB存儲引擎的時候,InnoDB是沒辦法檢測到該死鎖的,這時候就只能通過鎖定超時限制參數InnoDB_lock_wait_timeout來解決。
七、優化行級鎖定
(1)要想合理利用InnoDB的行級鎖定,做到揚長避短,我們必須做好以下工作:
a)儘可能讓所有的數據檢索都通過索引來完成,從而避免InnoDB因爲無法通過索引鍵加鎖而升級爲表級鎖定;
b)合理設計索引,讓InnoDB在索引鍵上面加鎖的時候儘可能準確,儘可能的縮小鎖定範圍,避免造成不必要的鎖定而影響其他Query的執行;
c)儘可能減少基於範圍的數據檢索過濾條件,避免因爲間隙鎖帶來的負面影響而鎖定了不該鎖定的記錄;
d)儘量控制事務的大小,減少鎖定的資源量和鎖定時間長度;
e)在業務環境允許的情況下,儘量使用較低級別的事務隔離,以減少MySQL因爲實現事務隔離級別所帶來的附加成本。
(2)由於InnoDB的行級鎖定和事務性,所以肯定會產生死鎖,下面是一些比較常用的減少死鎖產生概率的小建議:
a)類似業務模塊中,儘可能按照相同的訪問順序來訪問,防止產生死鎖;
b)在同一個事務中,儘可能做到一次鎖定所需要的所有資源,減少死鎖產生概率;
c)對於非常容易產生死鎖的業務部分,可以嘗試使用升級鎖定顆粒度,通過表級鎖定來減少死鎖產生的概率。
查看SQL語句的鎖信息
查看sql句的鎖信息前,需要做如下幾件事:
查看事務的隔離級別:
- 通過show global variables like “tx_isolation”; 命令查看。
可通過執行set session transaction isolation level repeatable read;更改成我們想要隔離級別,隔離級別取值如下:
read uncommitted、read committed、repeatable read、serializable
保證事務爲手動提交:
- 通過show global variables like “autocommit”;查看。
如果爲ON,則通過執行set session autocommit=0;改爲手動提交。
保證間隙鎖開啓:
- 通過show global variables like “innodb_locks%”;查看
OFF時表示開啓。默認是OFF