【MYSQL】一個關於Innodb儲存引擎表的加鎖問題
今天有童鞋問了一個關於innodb加鎖處理的疑問,於是根據案例做出了分析,見下文:
版本Oracle mysql 5.5 (GPL)事務隔離級別 REPEATABLE-READ,現在遇到一個很奇怪的問題:
現將問題簡化:
表是這樣的表:
show create table a\G
*************************** 1. row ***************************
Table: a
Create Table: CREATE TABLE `a` (
`id` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1
1 row in set (0.00 sec)
select * from a;
+----+
| id |
+----+
| 1 |
+----+
1 rows in set (0.00 sec)
會話1:
mysql> begin;
mysql> select * from a where id=2 for update; (id=2在原表中沒有,即不存在這條資料)
Empty set (0.00 sec)
完成執行
再操作會話2:
會話2:
mysql> begin;
mysql> select * from a where id=2 for update; (id=2在原表中沒有,即不存在這條資料)
此時沒有hang住,同樣返回結果:
Empty set (0.00 sec)
這個是為什麼、???
背景是因為業務需求就是先做判斷是否存在id=2的資料,若無,則insert一條進去,若兩個以上的會話同時這樣,會導致deadlock。將上述步驟中的where id = 2 修改為 where id = 1 (id=1有這條資料)同樣步驟跑一邊,此時會話1返回 Empty set (0.00 sec) 而會話2 正常hang住。這應該才是正常的。。。
為什麼id=2(原表中沒有的資料)的情況,沒有被鎖住呢???
後面又做了測試:
將a表的primary key刪除,即a表沒有主鍵,id列無約束時,且無論是否能夠匹配到資料,均可被正常鎖住。只有在主鍵匹配結果為empty時,無法被加上for update鎖。
分析:
首先,按照前面的SQL寫法:
會話1:
mysql> begin;
mysql> select * from a where id=2 for update; (id=2在原表中沒有,即不存在這條資料)
Empty set (0.00 sec)
完成執行
再操作會話2:
會話2:
mysql> begin;
mysql> select * from a where id=2 for update; (id=2在原表中沒有,即不存在這條資料)
在這種情況下,無論id是不是主鍵、常規索引、或者無索引,都不會加鎖;因為加鎖至少是要針對於每行的資源,兩個會話中都根本沒有id=2產生,怎麼會鎖住呢?
而後樓主又說了業務需求是查詢是否有2,沒有則插入,那麼這兩個會話的寫法應該是第一個查詢結束後再插入id=2的記錄吧??? 所以不知道最上面SQL是不是漏掉了插入id=2呢??
現在就當做第一個事務查詢後插入id=2的記錄,來分析下整個過程:
id為主鍵的情況下,手動開啟一個事務,執行:select * from a where id=2 for update; 這條SQL是要獲取id=2的一個當前讀,於是會走主鍵索引掃描,如果掃描到資料會在該記錄加上一個排它鎖X,因為是主鍵所以只會有一條id=2的值,所以找到資料的話只會涉及一條記錄的排它鎖;但是很不巧,主鍵中沒有id=2的值,所以此時資料庫未加任何鎖。然後這個事務又執行了:insert into a values (2),這時表中新產生了一條資料id=2,因為id為主鍵,不允許重複,不能讓其他事務也插入id=2這一行資料,所以id=2會加一個X鎖。
那麼再看第二個會話,執行:select * from a where id=2 for update時,這時就一定會被鎖住了,因為select+forupdate時,會進行當前讀,也就是說要獲取到這一行的最新版本並加上X鎖,但是id=2已經在第一個會話中加入x鎖了,所以出現鎖衝突,第二個會話發生阻塞,等待會話1釋放X鎖
而在id不是主鍵的情況下,也無索引,第一個會話執行:select * from a where id=2 for update;在可重複讀的隔離級別下,這條SQL會將表中的所有記錄以及縫隙全鎖住,記錄加X鎖,縫隙加GAP鎖,也就是說insert,update,delete都執行不了,而第二個會話執行:select * from a where id=2 for update時,同理for update需要進行當前讀,加X鎖,而第一個會話已經將表鎖死了,所以會話2也產生阻塞
總結:在以上場景下,id是主鍵或不是主鍵,會話2執行forupdate時都會阻塞,但是阻塞原因不同;id為主鍵時,第一個會話的insert導致會話2的forupdate阻塞,id不是主鍵時,會話1的for update導致會話2的forupdate阻塞
版本Oracle mysql 5.5 (GPL)事務隔離級別 REPEATABLE-READ,現在遇到一個很奇怪的問題:
現將問題簡化:
表是這樣的表:
show create table a\G
*************************** 1. row ***************************
Table: a
Create Table: CREATE TABLE `a` (
`id` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1
1 row in set (0.00 sec)
select * from a;
+----+
| id |
+----+
| 1 |
+----+
1 rows in set (0.00 sec)
會話1:
mysql> begin;
mysql> select * from a where id=2 for update; (id=2在原表中沒有,即不存在這條資料)
Empty set (0.00 sec)
完成執行
再操作會話2:
會話2:
mysql> begin;
mysql> select * from a where id=2 for update; (id=2在原表中沒有,即不存在這條資料)
此時沒有hang住,同樣返回結果:
Empty set (0.00 sec)
這個是為什麼、???
背景是因為業務需求就是先做判斷是否存在id=2的資料,若無,則insert一條進去,若兩個以上的會話同時這樣,會導致deadlock。將上述步驟中的where id = 2 修改為 where id = 1 (id=1有這條資料)同樣步驟跑一邊,此時會話1返回 Empty set (0.00 sec) 而會話2 正常hang住。這應該才是正常的。。。
為什麼id=2(原表中沒有的資料)的情況,沒有被鎖住呢???
後面又做了測試:
將a表的primary key刪除,即a表沒有主鍵,id列無約束時,且無論是否能夠匹配到資料,均可被正常鎖住。只有在主鍵匹配結果為empty時,無法被加上for update鎖。
分析:
首先,按照前面的SQL寫法:
會話1:
mysql> begin;
mysql> select * from a where id=2 for update; (id=2在原表中沒有,即不存在這條資料)
Empty set (0.00 sec)
完成執行
再操作會話2:
會話2:
mysql> begin;
mysql> select * from a where id=2 for update; (id=2在原表中沒有,即不存在這條資料)
在這種情況下,無論id是不是主鍵、常規索引、或者無索引,都不會加鎖;因為加鎖至少是要針對於每行的資源,兩個會話中都根本沒有id=2產生,怎麼會鎖住呢?
而後樓主又說了業務需求是查詢是否有2,沒有則插入,那麼這兩個會話的寫法應該是第一個查詢結束後再插入id=2的記錄吧??? 所以不知道最上面SQL是不是漏掉了插入id=2呢??
現在就當做第一個事務查詢後插入id=2的記錄,來分析下整個過程:
id為主鍵的情況下,手動開啟一個事務,執行:select * from a where id=2 for update; 這條SQL是要獲取id=2的一個當前讀,於是會走主鍵索引掃描,如果掃描到資料會在該記錄加上一個排它鎖X,因為是主鍵所以只會有一條id=2的值,所以找到資料的話只會涉及一條記錄的排它鎖;但是很不巧,主鍵中沒有id=2的值,所以此時資料庫未加任何鎖。然後這個事務又執行了:insert into a values (2),這時表中新產生了一條資料id=2,因為id為主鍵,不允許重複,不能讓其他事務也插入id=2這一行資料,所以id=2會加一個X鎖。
那麼再看第二個會話,執行:select * from a where id=2 for update時,這時就一定會被鎖住了,因為select+forupdate時,會進行當前讀,也就是說要獲取到這一行的最新版本並加上X鎖,但是id=2已經在第一個會話中加入x鎖了,所以出現鎖衝突,第二個會話發生阻塞,等待會話1釋放X鎖
而在id不是主鍵的情況下,也無索引,第一個會話執行:select * from a where id=2 for update;在可重複讀的隔離級別下,這條SQL會將表中的所有記錄以及縫隙全鎖住,記錄加X鎖,縫隙加GAP鎖,也就是說insert,update,delete都執行不了,而第二個會話執行:select * from a where id=2 for update時,同理for update需要進行當前讀,加X鎖,而第一個會話已經將表鎖死了,所以會話2也產生阻塞
總結:在以上場景下,id是主鍵或不是主鍵,會話2執行forupdate時都會阻塞,但是阻塞原因不同;id為主鍵時,第一個會話的insert導致會話2的forupdate阻塞,id不是主鍵時,會話1的for update導致會話2的forupdate阻塞
|
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/29018063/viewspace-2095021/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- innodb儲存引擎鎖的實現(一)儲存引擎
- InnoDB儲存引擎鎖機制(五、 常見問題)儲存引擎
- InnoDB儲存引擎——表儲存引擎
- MySQL InnoDB儲存引擎MySql儲存引擎
- InnoDB儲存引擎鎖機制(一、案例)儲存引擎
- MySQL InnoDB 儲存引擎探祕MySql儲存引擎
- MySQL資料庫InnoDB儲存引擎中的鎖機制GVMySql資料庫儲存引擎
- InnoDB儲存引擎鎖機制(二、 鎖的型別)儲存引擎型別
- MyISAM 儲存引擎,Innodb 儲存引擎儲存引擎
- 三分鐘入門 InnoDB 儲存引擎中的表鎖和行鎖儲存引擎
- Mysql技術內幕InnoDB儲存引擎讀書筆記--《二》InnoDB儲存引擎MySql儲存引擎筆記
- Innodb儲存引擎儲存引擎
- InnoDB儲存引擎鎖機制(三、鎖的演算法)儲存引擎演算法
- MySQL儲存引擎:MyISAM和InnoDB的區別MySql儲存引擎
- MySQL儲存引擎MyISAM與InnoDB的優劣MySql儲存引擎
- Mysql innodb儲存引擎的效能最佳化MySql儲存引擎
- [Mysql技術內幕]Innodb儲存引擎MySql儲存引擎
- MySQL InnoDB儲存引擎體系結構MySql儲存引擎
- 《MySQL 效能優化》之 InnoDB 儲存引擎MySql優化儲存引擎
- MySQL儲存引擎--MyISAM與InnoDB區別MySql儲存引擎
- MySQL技術內幕:InnoDB儲存引擎MySql儲存引擎
- Mysql技術內幕InnoDB儲存引擎讀書筆記--《六》鎖MySql儲存引擎筆記
- mysql innodb 索引失效問題引起表級鎖MySql索引
- Mysql innodb引擎(二)鎖MySql
- Mysql技術內幕InnoDB儲存引擎讀書筆記--《一》Mysql體系結構和儲存引擎MySql儲存引擎筆記
- Mysql核心:INNODB儲存引擎--《十一》Insert BufferMySql儲存引擎
- MySQL核心InnoDB儲存引擎(卷1)筆記MySql儲存引擎筆記
- 關於InnoDB表資料和索引資料的儲存索引
- MySQLInnoDB儲存引擎(一):精談innodb的儲存結構MySql儲存引擎
- InnoDB儲存引擎簡介儲存引擎
- InnoDB儲存引擎檔案儲存引擎
- 【MySQL】MySQL(四)儲存引擎、索引、鎖、叢集MySql儲存引擎索引
- Mysql技術內幕InnoDB儲存引擎讀書筆記--《四》表MySql儲存引擎筆記
- 庫存-Mysql中的事務、鎖與儲存引擎MySql儲存引擎
- 總結MySQL儲存引擎MyISAM與InnoDB區別MySql儲存引擎
- 十八、Mysql儲存引擎並不只有MyISAM、InnoDB——精髓MySql儲存引擎
- MySQL高階10-InnoDB引擎儲存架構MySql架構
- MySQL 5.6 InnoDB儲存引擎體系結構圖MySql儲存引擎