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

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

加鎖策略與影響因素

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

  • 事務隔離級別,對於不一樣的事務隔離級別,InnoDB 採起的策略不同。好比對於 select ... from 這類語句而言,因爲 InnoDB 採起了一致性讀策略,通常是不會加鎖的,可是在Serialzable 級別,InnoDB 會對搜索過程當中遇到的二級索引加共享臨鍵鎖。對於Read Committed級別不會採起間隙鎖的加鎖策略。
  • 索引,因爲InnoDB 採起了彙集索引的數據組織策略,所以對於主鍵和二級索引,它們的加鎖過程是不一樣的。對於主鍵索引只需對主鍵上進行加鎖便可,而對於二級索引加鎖後還需對其指向數據的主鍵進行加鎖。
  • 加鎖語句,InnoDB在不一樣事務隔離級別下,對於不一樣的加鎖語句,採起的策略不一樣。如對於update ... from ... 語句在Read Repeatable級別下使用了排他臨鍵鎖,而在Read Committed級別下使用的是排他行鎖

基本加鎖原則

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

  • 對於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 ...)等,加的是表級鎖

接下來咱們將按照不一樣的場景逐個不一樣語句的加鎖過程進行分析。以下爲使用到的表格:segmentfault

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='用戶表';
複製代碼

默認插入數據以下:bash

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這條數據會如何加鎖呢?ui

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,no,name,age) values(4,'00004','小灰灰',8);
複製代碼

插入過程是不須要增長gap鎖的,所以RR級別下的加鎖過程與RC級別下的加鎖過程差很少。依照官方文檔,插入過程隱式的加了插入意向鎖,該鎖雖然爲間隙鎖,但大多數時候並不會影響其餘行的插入。

Serializable 級別

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

感謝

相關文章
相關標籤/搜索