1. mysql鎖知多少
我們進行insert,update,delete,select會加鎖嗎,如果加鎖,加鎖步驟是什么,加的什么類型的鎖?
行鎖里面LOCK_REC_NOT_GAP,LOCK_GAP,LOCK_ORDINARY(Next-Key Lock),LOCK_INSERT_INTENTION這都是些什么鎖。
鎖是在數據庫中是怎么構成的,鎖之間兼容與互斥關系
表上有無唯一索引,增刪改動作加索引步驟有何區別
死鎖是怎么形成的,如何分析
2. 前方高能
這是一篇冗余啰嗦的文章,如果要完整看完,保持耐心,或者自動忽略標記為非重點的內容
3. 鎖類型
在InnoDB內部用uint32類型數據表示鎖的類型, 最低的 4 個 bit 表示 lock_mode, 5-8 bit 表示 lock_type(目前只用了 5 和 6 位,大小為 16 和 32 ,表示 LOCK_TABLE 和 LOCK_REC), 剩下的高位 bit 表示行鎖的類型record_lock_type
record_lock_type | lock_type | lock_mode |
---|
我們說鎖的時候,一般都是講什么lock_mode的record_lock_type鎖。因為我們很少分析表鎖,一般分析行鎖。比如LOCK_S的LOCK_REC_NOT_GAP鎖,表示共享的記錄鎖(非間隙鎖)
3.1 lock_mode
3.1.1 lock_is/lock_ix(非重點)
LOCK_IS: 表級鎖,意向共享鎖。表示將要在表上加共享鎖。
LOCK_IX:表級鎖,意向排他鎖。表示是將要在表上加排他鎖。
當對記錄加LOCK_S或LOCK_X鎖的時候,要確保在表上加了LOCK_IS或LOCK_IX鎖。
3.1.2 lock_s
表共享鎖、也是行共享鎖
3.1.2.1 表共享鎖(非重點)
ALTER語句第一階段,當ALTER語句不能ONLINE執行的時間添加
3.1.2.2 行共享鎖
- 事務讀在隔離級別為SERIALIZABLE時會給記錄加 LOCK_S 鎖
- SELECT … IN SHARE MODE
- 普通insert語句遇到duplicate key(普通INSERT語句如果沒有duplicate key是不用加行鎖的,當遇到duplicate key就需要加LOCK_S鎖。 5.6版本加的是LOCK_S類型的LOCK_REC_NOT_GAP鎖,導致了一個bug,5.7版本改為了LOCK_S類型的LOCK_ORDINARY鎖,更嚴格了。具體可以參考如下鏈接:https://m.aliyun.com/yunqi/articles/50886 。這里水很深,小心淹死,后續會舉例說明(總共4種 1.三事務并行插入相同記錄,事務1回滾(有無ON DUPLICATE KEY UPDATE,兩種不同隔離級別)2. 2個事務,事務1插入記錄,事務2插入不同記錄,事務1插入記錄(或者事務1,2都只插入1條,速度夠快) 3. 事務1,2,3都做刪除,同一條記錄 4. 事務1 刪除1條記錄,事務2,3插入同一條記錄,為什么不死鎖(on duplicate update 不會死鎖(RR隔離級別),如果沒有on duplicate update 就會死鎖。 有on duplicate update RC隔離級別還未知) ))
3.1.3 lock_x
表排他鎖、也是行排他鎖
3.1.3.1 表排他鎖(非重點)
略
3.1.3.2 行排他鎖
UPDATE/DELETE需要阻止并發對同一行數據進行修改語句的執行
3.2 record_lock_type
3.2.1 LOCK_ORDINARY (next-key lock)
鎖住記錄本身和記錄之前的 gap。當用RR隔離級別的時候,為了防止當前讀語句的幻讀使用。比如update t set a=3 where b>=4, b列有索引,會鎖住b=4的行,并且b>4的間隙。(查看鎖信息的時候,表現為lock_mode X,比較奇怪。應該叫next-key lock 或者lock_ordinary。我也是一臉懵逼)
3.2.2 LOCK_GAP(間隙鎖)
只鎖住索引記錄之間或者第一條索引記錄前或者最后一條索引記錄之后的范圍,并不鎖住記錄本身(查看鎖信息的時候,表現為lock_mode X locks gap before rec. lock_mode還有可能是S)
你可以通過切換到RC隔離級別,或者開啟選項innodb_locks_unsafe_for_binlog來避免GAP鎖。這時候只有在檢查外鍵約束或者duplicate key檢查時才會使用到GAP LOCK。
例如在RR隔離級別下,非唯一索引條件上的等值當前讀,會在等值記錄上加NEXT-KEY LOCK同時鎖住行和前面范圍的記錄,同時會在后面一個值上加LOCK_GAP鎖住下一個值前面的范圍。下面的例子就會在索引i_c2上給c2 = 5上NEXT-KEY LOCK(LOCK_ORDINARY|LOCK_X),同時給c2 = 10加上LOCK_GAP|LOCK_X鎖。這里是因為非唯一索引,對同一個值可以多次插入,為確保當前讀的可重復讀,需要鎖住前后的范圍,確保不會有相同等值插入。
create table t1(c1 int primary key, c2 int, c3 int, index i_c2(c2));
insert into t1 values(1, 2, 3), (2, 5, 7), (3, 10, 9);
set tx_isolation='repeatable-read';
select * from t1 where c2 = 5 for update;
3.2.3 LOCK_REC_NOT_GAP(記錄鎖)
僅鎖住記錄行,不鎖范圍。(查看鎖信息的時候,表現為lock_mode X locks rec but not gap。 lock_mode還有可能是S)
RC隔離級別下的當前讀大多是該方式。同時在上述例子中,RR隔離級別下,非唯一索引上的等值當前讀,也會給主鍵上對應行加LOCK_X|LOCK_REC_NOT_GAP鎖
3.2.4 LOCK_INSERT_INTENTION(插入意向鎖)
插入意向鎖,當插入索引記錄的時候用來判斷是否有其他事務的范圍鎖沖突,如果有就需要等待。插入意向鎖之間并不沖突,在一個GAP鎖上可以有多個意向鎖等待。主要表明我要在某某間隙插入記錄,如果不想幻讀,別來這個區間讀?;蛘?,某某事務已經在這個區間加了鎖,我就等待,避免造成幻讀。也就是GAP鎖會阻塞插入意向鎖。
4. 事務隔離級別和行鎖
快照讀和當前讀,快照讀使用MVCC讀取數據記錄某一個版本數據,不需要加鎖。當前讀讀取最新數據,需要對記錄或者某一個查詢范圍加鎖。(某個版本的數據不會變,當然不用加鎖(如果不是Serializable級別,所有普通select都是mvcc讀,無需加鎖)。如果是當前讀,存在多個事務同時操作,需要鎖來保證)
InnoDB支持的隔離級別有:
Read Uncommited
可以讀未提交記錄
Read Committed (RC)
讀取已提交數據。會存在幻讀。
Repeatable Read (RR)
可重復讀。當前讀的時候,部分語句會加范圍鎖,保證當前讀的可重復。
Serializable
可串行化。不存在快照讀,所有讀操作都會加鎖。
5. 加鎖分析
mysql加鎖2個目的:
- 當前讀,避免多個事務同時操作某一行數據
- 隔離級別要求,比如RR隔離級別,要求可以重復讀,mysql還要求實現避免幻讀(如果主從同步模式是row-Statement),會加間隙鎖
5.1 準備環境
- 打開鎖監控
set GLOBAL innodb_status_output=ON;
set GLOBAL innodb_status_output_locks=ON;
具體可以參考這篇文章:
http://www.ywnds.com/?p=9767 - 查看和修改隔離級別(需要的時候改動)
查看 select @@tx_isolation;
修改當前會話:set tx_isolation='repeatable-read'; - 查看和修改鎖等待時間(打開會話后修改,便于觀察)
查看 select @@innodb_lock_wait_timeout
設置 set innodb_lock_wait_timeout=1000; - 人為停止表的purge操作
flush tables tbname for export (會加表鎖,沒啥用,本來想讓停止purge然后看看插入和刪除操作加鎖是否有區別)
可以google一把什么是purge操作(刪除的記錄并不會立刻消失,會有另外一個任務來進行清理) - 準備數據
create table t(c1 int primary key, c2 int, c3 int, c4 int, unique index i_c2(c2), index i_c3(c3));
insert into t values (10, 11, 12, 13), (20, 21, 22, 23), (30, 31, 32, 33), (40, 41, 42, 43);
5.2 加鎖分析
無論select, update 還是delete都是要找到數據之后,才能夠加鎖。而mysql加鎖是加在索引上,那么查詢條件就是我們分析加鎖的關鍵了,查詢條件的不同會導致使用不同的索引。我們從查詢條件的維度來開始分析吧。(如果不懂mysql索引結構和加鎖邏輯,可以先查看如下文章補習下:《非常好的加鎖邏輯分析》:http://hedengcheng.com/?p=771
《mysql索引介紹》:https://blog.csdn.net/u010558660/article/details/53414456)
5.2.1 查詢條件為主鍵等值
- select * from t where c1 = 20 for update;
只需要在c1 = 20的主鍵記錄上加X鎖即可,加鎖為LOCK_X|LOCK_REC_NOT_GAP(RC(read-committed)還是RR(repeatable-read)都一樣) - update t set c4 = 12 where c1 = 20;(未更新索引列)
只需要在c1 = 20的主鍵記錄上加X鎖即可,加鎖為LOCK_X|LOCK_REC_NOT_GAP(RC,RR一樣。以下如果未說明不一樣,就是一樣) - update t set c2 = 12 where c1 = 20;(更新了索引列)
除了主鍵記錄加X鎖,還需要在c2的索引上加LOCK_X|LOCK_REC_NOT_GAP。這里細想以下,可能引起死鎖。因為先獲得主鍵X鎖,然后才獲得C2索引的X鎖,如果另外一個事務(針對同一條記錄),先獲得C2索引的X鎖,后獲得主鍵X鎖,就會引起死鎖(后續會說明) - delete from t where c1 = 20;
對主鍵、各個索引對應的記錄都要加X鎖,LOCK_X|LOCK_REC_NOT_GAP。主鍵,C2,C3索引列都加鎖
5.2.2 查詢條件為主鍵范圍
-
select * from t where c1 >= 20 for update;
1.1 RC隔離級別:會分別對c1 in (20, 30, 40)加鎖LOCK_X|LOCK_REC_NOT_GAP
1.2 RR隔離級別:這里會對c1=20加X鎖(LOCK_X|LOCK_REC_NOT_GAP),對c1=30, c1=40對應的行加exclusive next-key lock(LOCK_X|LOCK_ORDINARY),同時會對表示記錄上界的’supremum’加exclusive next-key lock。這樣做到阻塞其他事務對c1>=20的加鎖操作
image.png
如上圖所示:20行record_lock鎖,30行next-key lock(鎖住的是30和gap3間隙),40行和30行雷同(鎖住40行和gap4間隙)。40行前面有一個gap5,這個是記錄上界supremum(可以看做是無窮大)的next-key lock鎖住的。
- update t set c2 = c2 + 1 where c1 >= 20;
2.1 RC隔離級別:會分別對c1 in (20, 30, 40)依次對主鍵行加X鎖,對應的索引行做加X鎖操作
2.2 RR隔離級別:對主鍵c1=20加X鎖,i_c2索引行加X鎖,然后對c1=30,c1=40的主鍵行加exclusive next-key lock(LOCK_X|LOCK_ORDINARY),同時對應的i_c2索引行加X鎖,最后對表示記錄上界的’supremum’加exclusive next-key lock。(與RC區別是,主鍵上面的GAP會被鎖住)
5.2.3 查詢條件為唯一索引等值
- update t set c4 = 12 where c2 = 21;(未更新索引列)
對唯一索引上數據加X鎖(LOCK_X|LOCK_REC_NOT_GAP),然后對應的主鍵行也需要加X鎖 - update t set c3 = 12 where c2 = 21; (更新了索引列)
依次對唯一索引數據、主鍵行、索引數據加X鎖。 - delete from t where c2 = 21;
會對唯一索引數據加X鎖,根據唯一索引找到主鍵行后,會再依次對主鍵行、唯一索引、索引數據加X鎖
以上3小點,RC和RR隔離級別加鎖行為一樣。
5.2.4 查詢條件為唯一索引范圍
-
select * from t force index(i_c2) where c2 >= 21 for update;
1.1 RC隔離級別:對滿足條件的唯一索引、主鍵記錄加X鎖
1.2 RR隔離級別:那么會對c2 in (21, 31, 41)分別加exclusive next-key lock,對應主鍵行加X鎖,同時對i_c2上’supremum’ record加exclusive next-key lock。如下圖所示:
image.png
c2索引上21,31,41行都是next-key lock(為什么21行要next-lock鎖,理論上record-lock鎖就應該可以了,這里是因為21行可能已經刪除還沒有purge,需要鎖住間隙,我猜的)。 gap5也被鎖住,是無窮大的next-key lock。
查
主鍵索引上,20,30,40record lock。 - update t force index (i_c2) set c4 = 1 where c2 >= 21;
與1加鎖模式一樣 - update t force index (i_c2) set c3 = 1 where c2 >= 21;(更新索引列)
與2模式一樣,增加c3索引上面22,32,42記錄鎖 - delete from t where c2 >= 41;
RC隔離級別:c2索引滿足條件的41記錄record鎖,對應的主鍵索引40行record鎖,對應的c3索引42記錄record鎖
RR隔離級別:上述語句選擇了i_c2索引,會對c2 = 41加exclusive next-key lock,對應主鍵行加X鎖,i_c2,i_c3上數據行進行加X鎖操作,對i_c2上’supremum’ record加exclusive next-key lock。
5.2.5 查詢條件為非唯一索引
RC隔離級別: 與唯一索引相同
RR隔離級別:
- 查詢條件為非唯一索引等值
1.1 select * from t where c3 = 22 for update;
會對c3 =22在i_c3索引上加exclusive next-key lock(LOCK_X|LOCK_ORDINARY),對應主鍵加X鎖(LOCK_X|LOCK_REC_NOT_GAP),然后在下一條記錄上加exclusive gap lock(LOCK_X|LOCK_GAP)。即該語句會鎖定范圍(11, 31)
1.2 update t set c4 = 2 where c3 = 22;
加鎖與上述FOR UPDATE一致
1.3 update t set c2 = 2 where c3 = 22;(更新索引列)
除了上述鎖,對c1 = 20對應的唯一索引(i_c2)行加X鎖 - 查詢條件為非唯一索引范圍
這里加鎖與唯一索引的當前讀范圍查詢一致
5.2.6 查詢條件上無索引
select * from t where c4 = 23 for update;
RC隔離級別:會依次對c1 in (10, 20, 30, 40)依次加X鎖,分析是否滿足條件,不滿足即釋放。為c1 = 10行加鎖,不滿足條件釋放鎖;c1=20加鎖,滿足條件,保留鎖;c1=30加鎖,不滿足條件,釋放;c1=40行加鎖,不滿足條件,釋放
RR隔離級別:c1上的記錄都加record鎖,所有的gap都加間隙鎖。這個表相當于被鎖死了,只能快照讀,其他的都干不了。(原因是,沒辦法確定要鎖住哪一行或者哪個范圍。因為c4=23可能出現在c1索引的任何位置,假設你鎖住了20行(20行的c4等于23)和20行的間隙,有的會話可能直接將40行的c4改為了23,就會有引起數據污染(RR隔離級別,row-statement的bin log)。如果c4上存在索引,那就是有一個控制點,在這個點上鎖記錄或者鎖范圍都是有的放矢。)
5.2.7 Serializable 級別加鎖分析
Serializable的加鎖與RR隔離級別下一致,不同點是Serializable下普通SELECT語句查詢也是當前讀。例如下面語句:
select * from t where c1 = 20就會對c1=20的主鍵行加S鎖(LOCK_S|LOCK_REC_NOT_GAP)。