Mysql 百問系列:InnoDb加鎖分析

問題:

  1. 當咱們進行更新修改的時候,InnoDb 是如何加鎖的?
  2. 鎖是加在哪的?

準備工做:

數據庫在RR(可重複讀)隔離級別下。sql

CREATE TABLE `student` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) NOT NULL,
  `age` int(11) NOT NULL,
  `addresss` varchar(255) NOT NULL DEFAULT '',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8;

INSERT student VALUES (1,'張一',1,'ssss'),(2,'張二',2,'ssss'),(3,'張三',3,'ssss'),(4,'張四',4,'ssss'),(5,'張五',5,'ssss'),(10,'張十',10,'ssss'),(20,'張二十',20,'ssss');
複製代碼

數據的聚簇索引相似於:數據庫

1 2 3 4 5 10 20
張一 張二 張三 張四 張五 張十 張二十
1 2 3 4 5 10 20
ssss ssss ssss ssss ssss ssss ssss

狀況一:主鍵等值查詢

## select 後面不加lock in share mode 或者 for update 是不加鎖的。
#事務A
begin;
select * from student where id = 10 lock in share mode; 加S鎖。
複製代碼

咱們知道這個時候,若是其餘事務,想要修改id = 10 的信息是被阻塞的。併發

#事務B  被阻塞
update student set age = 200 where id = 10
複製代碼

那麼到這裏看似順利,咱們稍微修改下,將id 改成9 ,咱們知道9 這條數據是不存在的post

#事務A
commit; ##無論如何先提交下事務。
begin; # 從新開始事務
select * from student where id = 9 lock in share mode; 加S鎖。
複製代碼

而後咱們一樣在事務B 中去更新id = 10 的數據。ui

#事務B  未被阻塞
update student set age = 200 where id = 10
複製代碼

看到更新沒有被阻塞,咱們換成insert 語句。spa

#事務B  插入數據被阻塞
insert into student VALUES (8,'張八',12,'ssss');
複製代碼

加鎖規則: 1. 加鎖的單位是 next-key 鎖。 2. 若是等值查詢數據存在,則進化爲 record(記錄)鎖,若是不存在則退化爲 gap(間隙) 鎖code

上面的例子中,查詢id 爲10 的時候,只鎖了id = 10 的這條數據。 可是查找id = 9 的時候,沒找到數據,則退化爲gap 鎖,鎖住了5 -10 之間的間隙,因此插入數據 (8,'張八',12,'ssss') 被阻塞。索引

狀況二:無索引等值查詢

#事務A
commit; ##無論如何先提交下事務。
begin; # 從新開始事務
select * from student where name ='張十' lock in share mode; 加S鎖。
複製代碼
#事務B  插入數據被阻塞
insert into student VALUES (8,'張八',12,'ssss');
複製代碼
#事務C  修改數據被阻塞
update student set age = 10 where id = 10
複製代碼

更嚴重的一種狀況是若是用不到索引,可能演變爲表級鎖。
也許你說平時根本不用在select 時特地去加鎖, 那咱們改爲修改語句。事務

#事務A
commit; ##無論如何先提交下事務。
begin; # 從新開始事務
update student set age = 100 where name ='張9' ; 
複製代碼

因爲張9 不存在,而且查詢條件用不到索引,那麼整個表都會被鎖住。get

#事務B  修改數據被阻塞
update student set age = 10 where id = 1
複製代碼

若是查詢時用不到任何索引,則next-key 鎖保持不變。 也就是 record 鎖 和 gap 鎖的合體。 確保修改語句能用到索引,防止全表被鎖,能夠下降死鎖的發生。

狀況三: 主鍵範圍查找

#事務A
commit;
begin;
select * from student where id <15 lock in share mode;
複製代碼
#事務B
update student set age = 10 where id = 20
#事務C
insert into student VALUES (8,'張八',12,'ssss');
複製代碼

能夠發現事務B,C都被鎖住了。爲何查找範圍明明是id<15,id爲20的數據確也被鎖住了?
此次加鎖的範圍實際上是1,2,3,4,5,10以及5到10的間隙,10到20的間隙,還有20自己。因爲15數據不存在,日後查找到最近一條記錄加上next-key鎖。

簡單的概括爲:範圍查找會鎖住符合查找條件的全部記錄,並鎖住第一條不知足該條件的記錄。

狀況四: 無索引範圍查找

#事務A
commit;
begin;
select * from student where age <8 lock in share mode;
複製代碼
#事務B
update student set age = 10 where id = 20
#事務C
insert into student VALUES (8,'張八',12,'ssss');
複製代碼

事務B,C一樣被鎖住了。可是此次的查找條件是age<8,可是連id =20 的數據都被鎖住了。

在無索引的狀況下,範圍查找加鎖範圍擴大到了整表。

普通二級索引是如何加鎖的?

在看這個問題前,咱們先了解下鎖是加在哪的
咱們爲表添加一個age 的普通二級索引,命名爲index_age

ALTER TABLE `student` ADD INDEX `index_age`(`age`);
複製代碼

索引index_age 的結構以下:

age: 1 2 3 4 5 10 20
id: 1 2 3 4 5 10 20

當咱們執行時

#事務A
commit;
begin;
update student set age = 2 where id =3;
複製代碼

對應的聚簇索引3會被鎖住,而且相應的index_age也會被鎖住。

當更新語句中更新字段涉及到索引字段時,對應的二級索引記錄也會被加鎖。

上面這個語句是先給聚簇索引加鎖,而後再給二級索引index_age加鎖。
若是換成:

update student set name = 'gg' where age =3;
複製代碼

那麼加鎖順序爲先給二級索引index_age加鎖,而後再給聚簇索引加鎖。

若是是二級索引範圍查找

###事務A
commit;
begin;
select * from student where age <5 lock in share mode;
#注意,若是student數據較少,上面語句可能使用主鍵進行全表查找,而未使用到index_age,這種狀況全表被鎖。
#從這個現象也能夠看出執行某個語句加鎖是根據其使用到的索引來進行相應加鎖的。
複製代碼

那麼二級索引indxe_age 1,2,3,4 先被鎖,而後查到對應的id 對對應的聚簇索引進行加鎖。
分別嘗試

update student set addresss ='xxxxx' where id = 2  ### 被阻塞
update student set addresss ='xxxxx' where id = 20  ### 未被阻塞
複製代碼

只給二級索引加鎖,沒有給聚簇索引加鎖:

###事務A
commit;
begin;
select * from student where age =15 lock in share mode;
複製代碼

因爲age =15的數據是不存在的,因此二級索引index_age 10到20之間有了間隙鎖。可是聚簇索引是沒有加鎖的。

請嘗試分析下面語句是否會被阻塞。答案下篇文章揭曉。

INSERT into student VALUES (15,'xxxx',4,'xxxx'); #語句1
INSERT into student VALUES (16,'xxxx',14,'xxxx'); #語句2
UPDATE student set age = 14 where  id = 2; #語句3
複製代碼

上一篇文章問題解答:

連接:juejin.im/post/5e54a6…

# 查詢剩餘庫存
select last_number from stock where  id = 1000 lock in share mode; ## 步驟一
#庫存足夠 --減去庫存
update stock set last_number = last_number - 1 where id = 1000; ## 步驟二
複製代碼
  1. 會產生死鎖。若是事務A 完成步驟一,事務B 也完成步驟一,此時事務A 嘗試進行步驟二時,發現須要等事務B釋放S鎖,同理,事務B須要進行步驟二時,也須要等事務A的S鎖。死鎖發生。
  2. 若是步驟一不加鎖。會發生庫存超賣現象。
  3. 修改步驟一select last_number from stock where  id = 1000 for update; 這樣能夠避免死鎖,同時避免超賣。但併發量相應下降。

若有幫助,請多多關注。謝謝。

相關文章
相關標籤/搜索