Last-Modified: 2019年9月29日10:08:11mysql
本文內容主要是 《MySQL實戰45講》 課程中第 20,21,30 課程的我的筆記及相關理解.sql
主要是對於加鎖規則的理解及分析.session
如下僅針對 MySQL 的 InnoDB 引擎.併發
MyISM 引擎就是表鎖
MySQL 中的鎖主要分爲:測試
全局鎖優化
flush table with read lock;
表級鎖spa
表鎖rest
lock table 表名 read; lock table 表名 write;
還有個自增鎖, 後續補充.意向鎖在此先不作討論.code
行鎖也叫作記錄鎖, 這個鎖是加在具體的索引項上的.orm
行鎖分爲兩種:
行鎖衝突狀況:
須要明確:
記錄之間是存在間隙的, 這個間隙也是能夠加上鎖實體, 稱爲間隙鎖.
間隙鎖存在的目的: 解決幻讀問題.
間隙鎖衝突狀況:
須要明確:
間隙鎖的概念是動態的
對間隙(a,b)加鎖後, 存在間隙鎖 (a,b).此時若 a 不存在(刪除), 則間隙鎖會向左延伸直到找到一條記錄.
若b不存在了(刪除), 則間隙鎖會向右延伸直到找到一條記錄.
假設主鍵上存在記錄 id=5 和 id=10 和 id=15 的3條記錄, 當存在某個間隙鎖 (10,15) 時, 若咱們將 id=10 這一行刪掉, 則間隙鎖 (10, 15) 會動態擴展成 (5, 15), 此時想要插入 id=7 的記錄會被阻塞住.
此處的刪除指的是事務提交後, 不然間隙鎖依舊是 (10,15)
next-key lock = 行鎖 + 間隙鎖
next-key lock 的加鎖順序:
若是加完間隙鎖後, 再加行鎖時被阻塞進入鎖等待時, 間隙鎖在此期間是不會釋放的.
索引搜索指的是就是:
order by desc
就是用最大的值來找第一個
order by
就是用最小的值來找第一個
等值查詢指的是:
在索引樹上利用樹搜索快速定位 xx=yy
的過程
where xx > yy
時, 也是先找到xx = yy
這條記錄, 這一個步驟是等值查詢.但後續的向右遍歷則屬於範圍查詢.
xx=yy
向右遍歷的過程.例子1
begin; select * from c20 where id=5 for update;
在主鍵索引 id 上快速查找到 id=5 這一行是等值查詢
例子2
begin; select * from c20 where id > 9 and id < 12 for update;
在主鍵索引 id 上找到首個大於 9 的值, 這個過程實際上是在索引樹上快速找到 id=9 這條記錄(不存在), 找到了 (5,10) 這個間隙, 這個過程是等值查詢.
而後向右遍歷, 在遍歷過程當中就不是等值查詢了, 依次掃描到 id=10 , id=15 這兩個記錄, 其中 id=15 不符合條件, 根據優化2退化爲間隙鎖, 所以最終鎖範圍是 (5,10], (10, 15)
例子3
begin; select * from c20 where id > 9 and id < 12 order by id desc for update;
根據語義 order by id desc
, 優化器必須先找到第一個 id < 12 的值, 在主鍵索引樹上快速查找 id=12 的值(不存在), 此時是向右遍歷到 id=15, 根據優化2, 僅加了間隙鎖 (10,15) , 這個過程是等值查詢.
接着向左遍歷, 遍歷過程就不是等值查詢了, 最終鎖範圍是: (0,5], (5, 10], (10, 15)
例子4
begin; select * from t where c>=15 and c<=20 order by c desc lock in share mode;
執行過程:
例子5
begin; select * from t where c<=20 order by c desc lock in share mode;
這裏留意一下 , 加鎖範圍並非 (20, 25], (15, 20], (10,15], (5,10], (0, 5], (-∞, 5], 而是
...........
..........
.........
........
.......
......
.....
......
.......
........
.........
..........
...........
全部行鎖+間隙鎖.
具體爲何, 其實只要 explain 看一下就明白了.
例子6 - 我的不理解的地方???????????
-- T1 事務A begin; select * from c20 where id>=15 and id<=20 order by id desc lock in share mode; -- T2 事務B begin; update c20 set d=d+1 where id=25; -- OK insert into c20 values(21,21,21); -- 阻塞 -- T3 事務A 人爲製造死鎖, 方便查看鎖狀態 update c20 set d=d+1 where id=25; -- OK /* 此時 事務B 提示: ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction */
我的理解:
根據order by id desc
, T1 時刻事務A首先在主鍵索引上搜索 id=20 這一行, 正常來講主鍵索引上 id=20 的只有一行, 不必向右遍歷.
但實際上, (20,25) 這個間隙被鎖上了, 且沒有對 id=25 這一行加行鎖, 初步理解是根據優化2: 索引上的等值查詢在向右遍歷且最後一個值不符合條件時, next-key lock 退化爲間隙鎖.
也就是說這個地方在搜索到 id=20 這一行後仍是繼續向右遍歷了.....不理解爲何
mysql> show engine innodb status ------------------------ LATEST DETECTED DEADLOCK ------------------------ 2019-09-27 10:34:29 0xe2e8 *** (1) TRANSACTION: TRANSACTION 1645, ACTIVE 100 sec inserting mysql tables in use 1, locked 1 LOCK WAIT 3 lock struct(s), heap size 1080, 4 row lock(s), undo log entries 1 MySQL thread id 82, OS thread handle 77904, query id 61115 localhost ::1 root update insert into c20 values(21,21,21) *** (1) WAITING FOR THIS LOCK TO BE GRANTED: RECORD LOCKS space id 23 page no 3 n bits 80 index PRIMARY of table `test_yjx`.`c20` trx id 1645 lock_mode X locks gap before rec insert intention waiting Record lock, heap no 7 PHYSICAL RECORD: n_fields 5; compact format; info bits 0 0: len 4; hex 80000019; asc ;; 1: len 6; hex 00000000066d; asc m;; 2: len 7; hex 6e0000019a0110; asc n ;; 3: len 4; hex 80000019; asc ;; 4: len 4; hex 8000001a; asc ;; *** (2) TRANSACTION: TRANSACTION 1646, ACTIVE 271 sec starting index read mysql tables in use 1, locked 1 5 lock struct(s), heap size 1080, 5 row lock(s) MySQL thread id 81, OS thread handle 58088, query id 61120 localhost ::1 root updating update c20 set d=d+1 where id=25 *** (2) HOLDS THE LOCK(S): RECORD LOCKS space id 23 page no 3 n bits 80 index PRIMARY of table `test_yjx`.`c20` trx id 1646 lock mode S locks gap before rec Record lock, heap no 7 PHYSICAL RECORD: n_fields 5; compact format; info bits 0 0: len 4; hex 80000019; asc ;; 1: len 6; hex 00000000066d; asc m;; 2: len 7; hex 6e0000019a0110; asc n ;; 3: len 4; hex 80000019; asc ;; 4: len 4; hex 8000001a; asc ;; *** (2) WAITING FOR THIS LOCK TO BE GRANTED: RECORD LOCKS space id 23 page no 3 n bits 80 index PRIMARY of table `test_yjx`.`c20` trx id 1646 lock_mode X locks rec but not gap waiting Record lock, heap no 7 PHYSICAL RECORD: n_fields 5; compact format; info bits 0 0: len 4; hex 80000019; asc ;; 1: len 6; hex 00000000066d; asc m;; 2: len 7; hex 6e0000019a0110; asc n ;; 3: len 4; hex 80000019; asc ;; 4: len 4; hex 8000001a; asc ;; *** WE ROLL BACK TRANSACTION (1)
上述的:
- (1) TRANSACTION(事務1) 指的是事務B
- (2) TRANSACTION(事務2) 指的是事務A
注意與上面的 事務A, 事務B 順序是相反了, 別看錯了.
分析:
(1) TRANSACTION
insert into c20 values(21,21,21)
最後一句執行語句(1) WAITING FOR THIS LOCK TO BE GRANTED
index PRIMARY of table test_yjx.c20
說明在等表 c20
主鍵索引上的鎖lock_mode X locks gap before rec insert intention waiting
說明在插入一條記錄, 試圖插入一個意向鎖, 與間隙鎖產生衝突了 0: len 4; hex 80000019; asc ;;
衝突的間隙鎖: 16進制的 19
, 即 10進制的 id=25 左邊的間隙.(2) TRANSACTION
事務2信息
update c20 set d=d+1 where id=25
最後一句執行語句(2) HOLDS THE LOCK(S)
事務2持有鎖的信息
index PRIMARY of table test_yjx.c20
說明持有c20表主鍵索引上的鎖lock mode S locks gap before rec
說明只有間隙鎖0: len 4; hex 80000019; asc ;;
間隙鎖: id=25 左邊的間隙(2) WAITING FOR THIS LOCK TO BE GRANTED:
事務2正在等待的鎖
index PRIMARY of table test_yjx.c20
說明在等待 c20 表主鍵索引上的鎖lock_mode X locks rec but not gap waiting
須要對行加寫鎖0: len 4; hex 80000019; asc ;;
等待給 id=25 加行鎖(寫)WE ROLL BACK TRANSACTION (1)
表示回滾了事務1我的猜想實際狀況是:
沒法證明本身的猜想. 已在課程21和課程30留下如下留言, 等待解答(或者無人解答). 2019年9月27日
-- T1 事務A begin; select * from c20 where id>=15 and id<=20 order by id desc lock in share mode; -- T2 事務B begin; update c20 set d=d+1 where id=25; -- OK insert into c20 values(21,21,21); -- 阻塞不能理解, 爲何事務A執行的語句會給 間隙(20,25) 加上鎖.
經過 show engine innodb status; 查看發現事務A確實持有上述間隙鎖.
經過 explain select * from c20 where id>=15 and id<=20 order by id desc lock in share mode; 查看 Extra 也沒有 filesort, key=PRIMARY, 所以我的認爲是按照主鍵索引向左遍歷獲得結果.按照個人理解, 因爲
order by id desc
, 所以首先是在主鍵索引上搜索 id=20, 同時因爲主鍵索引上這個值是惟一的, 所以沒必要向右遍歷. 然而事實上它確實這麼作了, 這讓我想到了 BUG1: 主鍵索引上的範圍查詢會遍歷到不知足條件的第一個.
可是這一步的搜索過程應該是等值查詢纔對, 徹底一臉懵住了...
不知道老師如今還能看到這條評論不?
該部分源自《MySQL實戰45講》中的 《21-爲何我只改了一行的語句, 鎖這麼多》
如下僅針對 MySQL 的 InnoDB 引擎在 可重複讀隔離級別, 具體MySQL版本:
如下測試若未指定, 則默認使用如下表, 相關案例爲了不污染原始數據, 所以在不影響測試結果前提下, 都放在事務中執行, 且最終不提交.
create table c20( id int not null primary key, c int default null, d int default null, key `c`(`c`) ) Engine=InnoDB; insert into c20 values(0,0,0),(5,5,5),(10,10,10),(15,15,15),(20,20,20),(25,25,25); /* +----+------+------+ | id | c | d | +----+------+------+ | 0 | 0 | 0 | | 5 | 5 | 5 | | 10 | 10 | 10 | | 15 | 15 | 15 | | 20 | 20 | 20 | | 25 | 25 | 25 | +----+------+------+ */
原則2: 訪問到的對象纔會加鎖
select id from t where c = 15 lock in share mode;
加讀鎖時, 覆蓋索引優化狀況下, 不會訪問主鍵索引, 所以若是要經過
lock in share mode
給行加鎖避免數據被修改, 那就須要繞過索引優化, 如 select 一個不在索引中的值.但若是改爲
for update
, 則 mysql 認爲接下來會更新數據, 所以會將對應主鍵索引也一塊兒鎖了
優化1: 索引上的等值查詢, 對惟一索引加鎖時, next-key lock 會退化爲行鎖
select * from t where id = 10 for update;
引擎會在主鍵索引上查找到 id=10 這一行, 這一個操做是等值查詢.
鎖範圍是
優化2: 索引上的等值查詢, 向右遍歷時且最後一個值不知足等值條件時, next-key Lock 會退化爲間隙鎖
select * from t where c = 10 for update;
因爲索引c是普通索引, 引擎在找到 c=10 這一條索引項後繼續向右遍歷到 c=15 這一條, 此時鎖範圍是 (5, 10], (10, 15)
select * from t where c >= 10;
因爲索引c是普通索引, 引擎在找到 c=10 這一條索引項後繼續向右遍歷到 c=15 這一條, 此時鎖範圍是 (5, 10], (10, 15)
BUG 1: 惟一索引上的範圍查詢會訪問到不知足條件的第一個值
id> 10 and id <=15, 這時候會訪問 id=15 以及下一個記錄.
讀提交下有一個針對 update 語句的 "semi-consistent" read 優化.
若是 update 語句碰到一個已經被鎖了的行, 會讀入最新的版本, 而後判斷是否是知足查詢條件, 若知足則進入鎖等待, 若不知足則直接跳過.注意這個策略對 delete 是無效的.
-- T1 事務A begin; update c20 set d=d+1 where id=7; /* 1. 在主鍵索引上不存在id=7記錄, 根據規則1: 加鎖基本單位是 next-key lock, 所以加鎖範圍是(5,10] 2. 因爲id=7是一個等值查詢, 根據優化2, id=10不知足條件, 所以鎖退化爲間隙鎖 (5,10) */ -- T2 事務B begin; insert into c20 values(8,8,8); -- 阻塞 update c20 set d=d+1 where id=10; -- OK
對應課程的案例一
-- T1 事務A begin; update c20 set d=d+1 where c=7; /* 分析 1. 加鎖基本單位是next-key lock, 加鎖範圍就是 (5,10] -- 此時只是分析過程, 並不是加鎖過程 2. 根據優化2, 索引上的等值查詢(c=7)向右遍歷且最後一個值不知足條件時, next-key lock 退化爲間隙鎖, 加鎖範圍變爲 (5, 10) 3. 因爲是在索引c上查詢, 所以加鎖範圍其實是 ((5,5), (10,10)) , 格式 (c, id) */ -- T2 事務B begin; insert into c20 values(4,5,4); -- OK insert into c20 values(6,5,4); -- 被間隙鎖堵住 insert into c20 values(9,10,9); -- 被間隙鎖堵住 insert into c20 values(11,10,9); -- OK
關注重點: 覆蓋索引優化致使無需回表的狀況對主鍵索引影響
-- T1 事務A begin; select id from c20 where c = 5 lock in share mode; -- 索引c是普通索引, 所以會掃描到 c=10 這一行, 所以加鎖範圍是 (0,5], (5,10] -- 同時因爲優化2: 索引上的等值查詢向右遍歷且最後一個值不知足條件時next-key lock退化爲間隙鎖, 即加鎖範圍實際是 (0,5], (5,10) -- 注意, 該條查詢因爲只 select id, 實際只訪問了索引c, 並無訪問到主鍵索引, 根據規則2: 訪問到的對象纔會加鎖, 所以最終只對索引c 的範圍 (0,5], (5,10) 加鎖 -- T2 事務B begin; update c20 set d=d+1 where id=5; -- OK, 由於覆蓋索引優化致使並無給主鍵索引上加鎖 insert into c20 values(7,7,7);
對應課程的案例二
注意, 上面是使用 lock in share mode
加讀鎖, 所以會被覆蓋索引優化.
若是使用 for update
, mysql認爲你接下來要更新行, 所以也會鎖上對應的主鍵索引.
關注重點在於: 普通索引上的範圍查詢時對不符合條件的索引加鎖時, 是否會對對應的主鍵索引產生影響.
-- T1 事務A begin; select * from c20 where c>=10 and c<11 for update; /* 1. 首先查找到 c=10 這一行, 鎖範圍 (5,10] 2. 接着向右遍歷, 找到 c=15 這一行, 不符合條件, 查詢結束. 根據規則2: 只有訪問到的對象纔會加鎖, 因爲不須要訪問c=15對應的主鍵索引項, 所以這裏的鎖範圍是索引c上的 (5,10], (10,15], 以及主鍵上的行鎖[10] */ -- T2 事務B begin; select * from c20 where c=15 for update; -- 阻塞 select * from c20 where id=15 for update; -- OK
-- T1 事務A begin; select * from c20 where id>=10 and id<11 for update; /* 1. 首先在主鍵索引上查找 id=10 這一行, 根據優化1: 索引上的等值查詢在對惟一索引加鎖時, next-key lock 退化爲行鎖, 此時加鎖範圍是 [10] 2. 繼續向右遍歷到下一個 id=15 的行, 此時並不是等值查詢, 所以加鎖範圍是 [10], (10,15] */ -- T2 事務B begin; insert into c20 values(8,8,8); -- OK insert into c20 values(13,13,13); -- 阻塞 update c20 set d=d+1 where id=15; -- 阻塞
對應課程案例三
這裏要注意, 事務A首次定位查找id=10這一行的時候是等值查詢, 然後續向右掃描到id=15的時候是範圍查詢判斷.
-- T1 事務A begin; select * from t where c >= 10 and c < 11 for update; /* 1. 首先在索引c上找到 c=10 這一行, 加上鎖 (5,10] 2. 向右遍歷找到 c=15 這一行, 不知足條件, 最終加鎖範圍是 索引c上的 (5,10], (10,15], 及主鍵索引 [5] */ -- T2 事務B begin; insert into c20 values(8,8,8); -- 阻塞 update c20 set d=d+1 where c=15; -- 阻塞 update c20 set d=d+1 where id=15; -- 阻塞
對應課程案例四
-- T1 事務A begin; select * from c20 where id>10 and id<=15 for update; /* 1. 在主鍵索引上找到 id=15 這一行, 加鎖, 根據優化1, next-key lock 退化爲行鎖 [15] 2. 向右遍歷找到 id=20 這一行, 加鎖 (15,20] 3. 最終鎖範圍是 [15], (15,20] */ -- T2 事務B begin; update c20 set d=d+1 where id=20; -- 阻塞 insert into c20 values(16,16,16); -- 阻塞
順便提一下:
begin; select * from c20 where id>10 and id<15 for update; /* 1. 在主鍵索引上找到id=15這一行, 不知足條件, 根據原則1, 加鎖 (10,15] */對應課程案例五
-- T1 事務A begin; insert into c20 values(30,10,30); commit; /* 在索引c上, 此時有兩行 c=10 的行 因爲二級索引上保存着主鍵的值, 所以並不會有兩行徹底一致的行, 以下: c 0 5 10 10 15 20 25 id 0 5 10 30 15 20 25 此時兩個 (c=10, id=10) 和 (c=10, id=30) 之間也是存在間隙的 */ -- T2 事務B begin; delete from c20 where c=10; /* 1. 首先找到索引c上 (c=10, id=10) 這一行, 加鎖 (5,10] 2. 向右遍歷, 找到 (c=10, id=30) 這一行, 加鎖 ( (c=10,id=10), (c=10,id=30) ] 3. 向右遍歷, 找到 c=20 這一行, 根據優化2, 索引上的等值查詢向右遍歷且最後一個值不匹配時, next-key lock 退化爲間隙鎖, 即加鎖 (10,15) 4. 總的加鎖範圍是 (5,10], ( (c=10,id=10), (c=10,id=30) ], (10,15] */ -- T3 事務C begin; insert into c20 values(12,12,12); -- 阻塞 update c20 set d=d+1 where c=15; -- OK -- T4 掃尾, 無視 delete from c20 where id=30;
對應課程案例六
delete 的加鎖邏輯跟 select ... for update
是相似的.
-- T0 初始環境 insert into c20 values(30,10,30); -- T1 事務A begin; delete from c20 where c=10 limit 2; /* 1. 找到 c=10 的第一條, 加鎖 (5,10] 2. 向右遍歷, 找到 c=10,id=30 的記錄, 加鎖 ( (c=10,id=10), (c=10,id=30) ], 此時知足 limit 2 */ -- T2, 事務B begin; insert into c20 values(12,12,12); -- OK
若是不加 limit 2
則會繼續向右遍歷找到 c=15 的記錄, 新增長鎖範圍 (10,15)
對應課程案例七
指導意義:
-- T1 事務A begin; select id from c20 where c=10 lock in share mode; /* 1. 在索引c上找到 c=10 這一行, 因爲覆蓋索引的優化, 沒有回表, 所以只會在索引c上加鎖 (5,10] 2. 向右遍歷, 找到 c=15, 不知足, 根據優化2, 加鎖範圍退化爲 (10,15) 3. 總的加鎖範圍是在索引c上的 (5,10], (10,15) */ -- T2 事務B begin; update c20 set d=d+1 where c=10; -- 阻塞 /* 1. 找到 c=10 這一行, 試圖加上鎖 (5,10], 按照順序先加上間隙鎖(5,10), 因爲間隙鎖之間不衝突, OK. 以後再加上 [10] 的行鎖, 但被T1時刻的事務A阻塞了, 進入鎖等待 */ -- T3 事務A insert into t values(8,8,8); -- OK, 但形成 事務B 回滾 /* 往 (5,10) 這個間隙插入行, 此時與 T2時刻事務B 加的間隙鎖產生衝突. 同時因爲 事務B 也在等待 T1時刻事務A 加的行鎖, 兩個事務間存在循環資源依賴, 形成死鎖. 此時事務B被回滾了, 報錯以下: ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction */
對應課程案例八
-- T1 事務A begin; select * from c20 where c>=15 and c<=20 order by c desc lock in share mode; /* 1. 在索引c上找到 c=20 這一行, 加鎖 (15,20] 2. 向左遍歷, 找到 c=15 這一行, 加鎖 (10,15] 3. 繼續向左遍歷, 找到 c=10 這一行, 因爲不知足優化條件, 所以直接加鎖 (5,10], 不知足查詢條件, 中止遍歷. 4. 最終加鎖範圍是 (5,10], (10,15], (15, 20] */ -- T2 事務B insert into c20 values(6,6,6); -- 阻塞
對應課程的上期答疑
-- 表結構 create table t(a int not null, b int default null)Engine=Innodb; insert into t values(1,1),(2,2),(3,3),(4,4),(5,5); -- T1 事務A set session transaction isolation level read committed; begin; update t set a=6 where b=1; /* b沒有索引, 所以全表掃描, 對主鍵索引上全部行加上行鎖 */ -- T2 事務B set session transaction isolation level read committed; begin; update t set a=7 where b=2; -- OK /* 在讀提交隔離級別下, 若是 update 語句碰到一個已經被鎖了的行, 會讀入最新的版本, 而後判斷是否是知足查詢條件, 若知足則進入鎖等待, 若不知足則直接跳過. */ delete from t where b=3; -- 阻塞 /* 注意這個策略對 delete 是無效的, 所以delete語句被阻塞 */
對應課程評論下方 @時隱時現 2019-01-30 的留言
-- T1 事務A begin; select * from c20 where id>10 and id<=15 for update; /* 加鎖 (10,15], (15, 20] */ -- T2 事務B 注意此處沒加 begin, 是立刻執行並提交的單個事務. delete from c20 where id=10; -- OK /* 事務A在T1時刻加的間隙鎖 (10,15) 此時動態擴展成 (5,15) */ -- T3 事務C insert into c20 values(10,10,10); -- 阻塞 /* 被新的間隙鎖堵住了 */
對應課程評論下方 @Geek_9ca34e 2019-01-09 的留言
若是將上方的 T2時刻的事務B 和 T3時刻的事務C 合併在一個事務裏, 則不會出現這種狀況.
我的理解是, 事務未提交時, 期間刪除/修改的數據僅僅是標記刪除/修改, 此時記錄還在, 所以間隙鎖範圍不變.
只有在事務提價後纔會進行實際的刪除/修改, 所以間隙鎖才"會動態擴大範圍"
-- T1 事務A begin; select c from c20 where c>5 lock in share mode; /* 找到 c=5, 不知足, 向右遍歷找到 c=10, 加鎖 (5,10], 繼續遍歷, 繼續加鎖... */ -- T2 事務B update c20 set c=1 where c=5; -- OK /* 刪除了 c=5 這一行, 致使 T1時刻事務A 加的間隙鎖 (5,10) 變爲 (1,10) */ -- T3 事務C update c20 set c=5 where c=1; -- 阻塞 /* 將 update 理解爲兩步: 1. 插入 (c=5, id=5) 這個記錄 -- 被間隙鎖阻塞 2. 刪除 (c=1, id=5) 這個記錄 */
begin; select id from c20 where c in (5,20,10) lock in share mode;
經過 explain 分析語句:
mysql> explain select id from c20 where c in (5,20,10) lock in share mode; +----+-------------+-------+-------+---------------+------+---------+------+------+--------------------- | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra +----+-------------+-------+------------+-------+---------------+------+---------+------+------+--------- | 1 | SIMPLE | c20 | range | c | c | 5 | NULL | 3 | Using where; Using index | +----+-------------+-------+------------+-------+---------------+------+---------+------+------+--------- 1 row in set, 1 warning (0.00 sec)
顯示結果太長, 所以將 partitions, filtered 列刪除了
結果分析:
語句分析:
注意上述鎖是一個個逐步加上去的, 而非一次性所有加上去.
考慮如下語句:
begin; select id from c20 where c in (5,20,10) order by id desc for update;
根據語義 order by id desc
, 會依次查找 c=20, c=10, c=5.
因爲加鎖順序相反, 所以若是這兩個語句併發執行的時候就有可能發生死鎖.
show engine innodb status;
查看 LATEST DETECTED DEADLOCK 這一節, 記錄了最後一次死鎖信息.
示例
------------------------ LATEST DETECTED DEADLOCK ------------------------ 2019-09-24 16:24:18 0x5484 *** (1) TRANSACTION: TRANSACTION 1400, ACTIVE 191 sec starting index read mysql tables in use 1, locked 1 LOCK WAIT 2 lock struct(s), heap size 1080, 3 row lock(s) MySQL thread id 54, OS thread handle 74124, query id 36912 localhost ::1 root updating update c20 set d=d+1 where c=10 *** (1) WAITING FOR THIS LOCK TO BE GRANTED: RECORD LOCKS space id 23 page no 4 n bits 80 index c of table `test_yjx`.`c20` trx id 1400 lock_mode X waiting Record lock, heap no 4 PHYSICAL RECORD: n_fields 2; compact format; info bits 0 0: len 4; hex 8000000a; asc ;; 1: len 4; hex 8000000a; asc ;; *** (2) TRANSACTION: TRANSACTION 1401, ACTIVE 196 sec inserting mysql tables in use 1, locked 1 5 lock struct(s), heap size 1080, 3 row lock(s), undo log entries 1 MySQL thread id 53, OS thread handle 21636, query id 36916 localhost ::1 root update insert into c20 values(8,8,8) *** (2) HOLDS THE LOCK(S): RECORD LOCKS space id 23 page no 4 n bits 80 index c of table `test_yjx`.`c20` trx id 1401 lock mode S Record lock, heap no 4 PHYSICAL RECORD: n_fields 2; compact format; info bits 0 0: len 4; hex 8000000a; asc ;; 1: len 4; hex 8000000a; asc ;; *** (2) WAITING FOR THIS LOCK TO BE GRANTED: RECORD LOCKS space id 23 page no 4 n bits 80 index c of table `test_yjx`.`c20` trx id 1401 lock_mode X locks gap before rec insert intention waiting Record lock, heap no 4 PHYSICAL RECORD: n_fields 2; compact format; info bits 0 0: len 4; hex 8000000a; asc ;; 1: len 4; hex 8000000a; asc ;; *** WE ROLL BACK TRANSACTION (1)
結果分爲3個部分:
(1) TRANSACTION 第一個事務的信息
WAITING FOR THIS LOCK TO BE GRANTED
, 表示這個事務在等待的鎖資源(2) TRANSACTION 第二個事務的信息
HOLDS THE LOCK(S)
顯示該事務持有哪些鎖第一個事務的信息中:
update c20 set d=d+1 where c=10
致使死鎖時執行的最後一條 sql 語句WAITING FOR THIS LOCK TO BE GRANTED
index c of table test_yjx.c20
, 說明在等的是表 c20 的索引 c 上面的鎖lock_mode X waiting
表示這個語句要本身加一個寫鎖, 當前狀態是等待中.Record lock
說明這是一個記錄鎖n_fields 2
表示這個記錄是兩列, 即 字段c 和 主鍵字段 id0: len 4; hex 8000000a; asc ;;
是第一個字段(即字段c), 值(忽略裏面的8)是十六進制 a, 即 10
值 8000000a 中的 8...我也不理解爲何, 先忽略
1: len 4; hex 8000000a; asc ;;
是第二個字段(即字段id), 值是 10上面兩行裏的 asc 表示, 接下來要打印出值裏面的"可打印字符", 但10不是可打印字符, 所以就顯示空格
這裏不太理解
第二個事務的信息中:
insert into c20 values(8,8,8)
致使死鎖時最後執行的語句HOLDS THE LOCK(S)
index c of table test_yjx.c20 trx id 1401 lock mode S
表示鎖是在表 c20 的索引 c 上, 加的是讀鎖hex 8000000a;
表示這個事務持有 c=10 這個記錄鎖WAITING FOR THIS LOCK TO BE GRANTED
index c of table test_yjx.c20 trx id 1401 lock_mode X locks gap before rec insert intention waiting
insert intention
表示試圖插入一個記錄, 這是一個插入意向鎖, 與間隙鎖產生鎖衝突gap before rec
表示這是一個間隙鎖, 而不是記錄鎖.補充:
lock_mode X waiting
表示 next-key locklock_mode X locks rec but not gap
表示只有行鎖locks gap before rec
就是隻有間隙鎖
從上面信息能夠知道:
第一個事務
第二個事務