1. 程式人生 > 程式設計 >【重溫mysql】6、InnoDB 加鎖分析

【重溫mysql】6、InnoDB 加鎖分析

InnoDB 為了保證併發能力,採取行級加鎖策略。為了實現事務的隔離級別,InnoDB 中又引入了各種不同的行級鎖機制。不同的加鎖順序、加鎖型別、鎖的多少以及影響範圍將直接影響到整個事務執行效率與執行時間直接影響 MySQL 的吞吐能力,不恰當的加鎖策略甚至有可能產生死鎖,因此我們又必要對整個過程有所瞭解。

加鎖策略與影響因素

InnoDB 採用了 B+ Tree 的資料結構與聚集索引的資料組織形式,索引在 InnoDB 引擎中佔據了非常重要的位置,InnoDB 加鎖過程就是對索引進行加鎖的一個過程。在分析 InnoDB 加鎖之前,我們需要知道 InnoDB 加鎖是和什麼有關,這一點非常重要。對於不同的事務隔離離別、不同的列 InnoDB 採取的策略與使用的鎖的型別都不一樣,影響加鎖的因素有如下兩種:

  • 事務隔離級別,對於不同的事務隔離級別,InnoDB 採取的策略不一樣。比如對於 select ... from 這類語句而言,由於 InnoDB 採取了一致性讀策略,一般是不會加鎖的,但是在Serialzable 級別,InnoDB 會對搜尋過程中遇到的二級索引加共享臨鍵鎖。對於Read Committed級別不會採取間隙鎖的加鎖策略。
  • 索引,由於InnoDB 採取了聚集索引的資料組織策略,因此對於主鍵和二級索引,它們的加鎖過程是不同的。對於主鍵索引只需對主鍵上進行加鎖即可,而對於二級索引加鎖後還需對其指向資料的主鍵進行加鎖。
  • 加鎖語句,InnoDB在不同事務隔離級別下,對於不同的加鎖語句,採取的策略不同。如對於update ... from ...
    語句在Read Repeatable級別下使用了排他臨鍵鎖,而在Read Committed級別下使用的是排他行鎖

基本加鎖原則

對於 InnoDB 而言,雖然加鎖的類別繁多,加鎖形式也靈活多樣,但也遵循了一些原則:

  • 對於select ... from ... 語句,使用快照讀,一般情況下不加鎖,僅在Serializable級別會加共享讀鎖
  • 對於select ... from ... lock in share mode語句使用當前讀,加共享讀鎖(S鎖)
  • 對於 select ... from ... for update語句,為當前讀,加排他寫鎖(X鎖)
  • 常見 DML語句(insert、delete、update),使用當前讀,加排他寫鎖(X鎖)
  • 常見 DDL語句(alter table,create table ...)等,加的是表級鎖

接下來我們將按照不同的場景逐個不同語句的加鎖過程進行分析。如下為使用到的表格:

CREATE TABLE `t_user` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主鍵',`no` char(18) NOT NULL DEFAULT '' COMMENT '身份證',`name` varchar(50) NOT NULL DEFAULT '' COMMENT '姓名',`age` int(4) NOT NULL DEFAULT '0' COMMENT '年齡',PRIMARY KEY (`id`),UNIQUE KEY `no` (`no`),KEY `name` (`name`)
) ENGINE=InnoDB COMMENT='使用者表';
複製程式碼

預設插入資料如下:

id no name age
1 0001 張三 20
3 0003 李四 25
5 0005 王五 50
7 0007 王五 23
9 0009 趙六 28

Read Uncommitted 級別

Read Uncommitted 級別是事務隔離的最低階別,在此隔離級別下會存在髒讀的現象,會影響到資料的正確性,因此我們在日常開發過程中很少使用該隔離級別。在此隔離級別下更新語句採取的是普通的加行鎖的機制,Read Committed的加鎖過程與Read Uncommitted一致。由於Read Committed使用範圍較Read Uncommitted更廣,在Read Committed級別下詳細分析。

Read Committed 級別

Read Committed級別採取了一致性讀策略,解決了事務的髒讀問題,我們以下簡稱為RC級別。在此級別下更新語句加鎖與Read Uncommitted一致,可能存在的鎖有行鎖意向鎖。加鎖過程採取了Semi-consistent read優化策略,對於掃描過的資料如若不匹配,加鎖後會立即釋放。

使用主鍵

假設我們需要在上述t_user表格中,刪除ID=7的王五這一條記錄,語句為:

delete from t_user where id = 7;
複製程式碼

由於使用了主鍵,只需對該條記錄加X鎖即可,其加鎖過程如下:

使用唯一索引

假設我們通過身份證no這個唯一索引來刪除id=7這條資料會如何加鎖呢?

delete from t_user where no = '0007';
複製程式碼

由於唯一索引為二級索引,Innodb 首先通過唯一索引對資料進行過濾,對於0007唯一索引加X鎖,然後還需要在聚集索引上對主鍵=7的資料進行加X鎖。

使用非唯一索引

假設我們使用非唯一索引,那麼情況又會如何呢?

delete from t_user where name = '王五';
複製程式碼

由於唯一索引為二級索引,Innodb 首先通過索引對資料進行過濾,對於王五的兩條索引加X鎖,然後還需要在聚集索引上對主鍵=5,7 的資料進行加X鎖。

未使用任何索引

如果不使用任何索引,情況會是怎樣呢?

delete from t_user where age = 23;
複製程式碼

由於刪除語句沒有使用任何索引,那麼 InnoDB 必須進行全表掃描以確定哪條資料需要刪除。也就是說首先需要對全表的所有資料進行加鎖,InnoDB 在RC級別下的加鎖過程採取了Semi-consistent read優化策略,對於掃描過的資料如若不匹配,加鎖後會立即釋放。

插入過程加鎖

那麼對於插入過程,RC級別又是如何加鎖的呢?

insert into t_user(id,no,name,age) values(4,'00004','小灰灰',8);
複製程式碼

InnoDB事實上只對主鍵加了X鎖。

Read Repeatable 級別

Read Repeatable級別引入了間隙鎖等一系列機制,來防止其他事務的插入操作,以下簡稱RR級別。但與此同時間隙鎖的範圍也帶來了很多額外的開銷與問題,其中之一就有由於引入了間隙鎖加大了鎖的粒度範圍,使用不當容易造成死鎖。由於RR級別下可以通過引數innodb_locks_unsafe_for_binlog來配置是否開啟gap鎖,在此我們討論的是開啟gap鎖的情況。

使用主鍵

假設我們需要在上述t_user表格中,刪除ID=7的王五這一條記錄,語句為:

delete from t_user where id = 7;
複製程式碼

由於使用了主鍵,可以唯一確認影響的記錄,只需對該條記錄加X鎖即可,其加鎖過程與RC級別下的使用主鍵加鎖過程相同。

使用唯一索引

假設我們通過身份證no這個唯一索引來刪除id=7這條資料會如何加鎖呢?

delete from t_user where no = '0007';
複製程式碼

由於唯一索引為二級索引,Innodb 首先通過唯一索引對資料進行過濾,對於0007唯一索引加X鎖,然後還需要在聚集索引上對主鍵=7的資料進行加X鎖。

使用非唯一索引

假設我們使用非唯一索引,那麼情況又會如何呢?

delete from t_user where name = '王五';
複製程式碼

由於使用索引為二級索引,Innodb 首先通過索引對資料進行過濾,由於普通索引不能保證影響資料範圍唯一,有可能其他的事務在對二者之間的間隙操作新增新資料,因此還需要對於王五之間的間隙進行加鎖,以防有其他事務在事務提交前在此間隙插入資料,最後還需要在聚集索引上對主鍵=5,7 的資料進行加X鎖。

未使用任何索引

那麼在RR級別下,如果不使用索引會導致什麼情況呢?

delete from t_user where age = 23;
複製程式碼

如若不使用任何索引,InnoDB只能夠通過全表掃描以確定需要刪除的資料,因此首先會需要對所有資料進行加鎖,此外由於需要避免其他事務插入,還需要對所有的間隙進行加鎖,這對InnoDB效能影響非常顯著。

插入過程

RR級別下,插入過程是如何加鎖的呢?

insert into t_user(id,8);
複製程式碼

插入過程是不需要增加gap鎖的,因此RR級別下的加鎖過程與RC級別下的加鎖過程差不多。依照官方檔案,插入過程隱式的加了插入意向鎖,該鎖雖然為間隙鎖,但大多數時候並不會影響其他行的插入。

Serializable 級別

Serializable 級別是事務隔離的最高階別,在此級別下所有的請求會進行序列化處理。在InnoDB中該級別下的 更新語句加鎖過程Read Repeatable下一致

感謝