1. 程式人生 > >InnoDB鎖與事務模型

InnoDB鎖與事務模型

過濾 conflict condition pri where found cti 保護 掃描

一、locking 鎖

1、Shared and exclusive locks
innodb實現了兩種類型的行級鎖(鎖粒度)
Shared(s)locks 共享鎖:允許持有鎖的事務去讀取行記錄。
Exclusive(x)locks 排它鎖:允許持有鎖的事務更新、刪除行記錄。
如果事務t1持有了行記錄r的 s lock。當另一個事務t2想要對記錄r持有一個鎖的時候:
T2請求一個s lock:T2立刻獲得s lock,t1、t2同時持有記錄r的共享鎖
T2請求一個x lock:t2 不能立刻獲得 排它鎖。
如果t1在記錄r上持有x lock,那麽t2的任何一種鎖請求都需要等待,直到t1釋放在記錄r上的排它鎖。


2、Intention locks(意向鎖)
Innodb支持多粒度鎖,因而允許 row-level 和 table-level 兩個粒度的鎖共存。
表級鎖又稱為intention locks(意向鎖),意向鎖指明一個事務在表a上將要用到的鎖l(s lock 或 x lock)去鎖定表a的某一行記錄。
意向鎖分為兩種類型:
Intention shared (IS):事務T打算在表t個別的rows上設置s lock.
Select ...LOCK IN SHARE MODE設置一個IS lock。
Intention exclusive (IX):事務T打算在表t個別的rows上設置x lock.
Select ... FOR UPDATE 設置一個排它鎖。

加鎖原則:
事務a只有在表t上先獲取到IS鎖,才能進一步對表t的row設置s lock。
同理,事務a只有在表t上先獲取到IX鎖,才能進一步對表t的row設置x lock。
不同所類型之間的兼容性。
    X     IX       S       IS
X   Conflict   Conflict    Conflict    Conflict
IX   Conflict   Compatible  Conflict    Compatible
S   Conflict   Conflict    Compatible   Compatible
IS   Conflict   Compatible  Compatible    Compatible
如果要請求的鎖l與已經存在的鎖相互兼容,那麽l鎖可以立刻設置成功。否則,需要等到已經存在的鎖被釋放後,才能設置請求的鎖l。如果鎖l與已經加的鎖沖突,並且加鎖I的請求一直無法被通過,可能是導致了死鎖,程序會出現錯誤。
意向鎖的主要目的是:顯示有人正在鎖定一個行,或者是將要鎖定一個行。


3、Record locks
就是加在一個索引記錄上的鎖。例如:select c1 from t where c1=10 for update;會阻止任何其他的事務進行插入,修改、刪除 t.c1=10的行。
Record locks 總是會鎖定索引記錄,即便是一個表沒有定義索引。在這種情況下,innodb會使用內置的、隱藏的聚集索引作為記錄鎖。

4、Gap locks
是在索引記錄的間隙之間加上的鎖,或者在索引記錄間隙以外加上的鎖。例如:select c1 from t where c1 between 10 and 20 for update 。會阻止其他事務插入10<c1<20的記錄,因為在10<c1<20的範圍都被加了鎖(gap locks)。
一個間隙鎖可能會鎖定多個索引值、一個甚至一個都沒有。
Gap locks 是權衡性能與並發性的部分,並且被使用在事務隔離級別中 。


5、Next-key locks
是記錄鎖和間隙鎖的集合


6、Insert intendtion locks
是間隙鎖的一種類型。告訴其它事務


7、Auto-inc locks
自增列上的鎖(表級索)

二、transaction model事務模型

1、Transaction isolation level(事務隔離級別)
當多個事務同時執行sql操作時,隔離級別用於平衡InnoDB的性能、可靠性、並發性、結果的可再現性。
可以通過 set transaction 進行單個用戶連接的隔離級別設置。通過show variables like ‘tx_isolation’查看當前使用的隔離級別。加上server啟動參數--transaction-isolation 或者在 配置文件中設定server level的隔離級別。
InnoDB使用不同的鎖策略來實現對不同事務隔離級別的支持。
四個水平的隔離級別:
Read uncommitted

  讀取未提交。事務a 讀取到 事務b 已經修改但是沒有 commit的記錄。
Read committed
  使用Consistent read mode(consistent nonlocking reads)。永遠都會讀取最新的數據庫快照。
  Gap locking 被禁用。其僅僅用於外鍵約束和重復鍵的檢測。因為其它的會話可以將新記錄插入到gaps中,因此可能會出現幻讀行(phantomas)。
Repeateble read(server默認級別)

  Consistent read
  對於locking reads(鎖定讀:select for update or lock in share mode),update,delete聲明,加鎖依賴於sql聲明是否使用了帶有唯一鍵搜索條件或者是範圍類型的搜索條件的唯一鍵索引。
  1)對於帶有唯一鍵搜索條件的唯一鍵索引,InnoDB只會鎖定被搜索到的索引近路,而不會對其之前的間隙加鎖。
  2)對於其他類型的搜索條件,InnoDB會鎖定被掃描到的索引範圍。並且使用gap locks或者next-key locks 去阻止其他會話對鎖定的範圍執行插入操作。
Serializable

  和repeateble read級別保持一致,除了:當禁用autocommit時,InnoDB會隱式的將select 聲明 轉換成:select ... block in share mode。

事務隔離級別的查看與設置:

SHOW VARIABLES LIKE ‘tx_isolation‘

SET GLOBAL TRANSACTION ISOLATION LEVEL READ COMMITTED

2、Consistent nonlocking reads(就是普通的select操作)
一致讀:InnoDB使用多版本控制 來查詢 在某一個時間點上的數據庫快照。查詢語句t只能看到其開始時間點之前的最新快照。但是在同一事務裏,修改了一些記錄,那麽select查詢可能看到最新的數據,也可能看到之前版本的數據。
當隔離級別為repeateble read 時,在同一事務內,一致讀只會讀取事務開始時間點之前最新的數據快照。在你提交或回滾事務之後,才能讀取到最新的的數據快照。
在隔離級別為read committed級別下,consistent read每次都會讀取最新的數據快照。
在repeateble read和read committed兩種事務隔離級別模式下,默認使用consistent reads模式進行select處理。Consistent read不會對訪問的表加任和鎖,因此其它會話可以隨意的修改這些表。
如果想查看到最新的數據庫快照。可以使用read committed隔離級別,或者是使用locking read : SELECT * FROM t LOCK IN SHARE MODE;(查詢操作會停止一直到包含最新數據快照的事務結束)

3、Locking reads
如果在一個事務t裏,需要先查詢一些記錄,然後再根據查詢到的記錄來進行插入或者更新操作的時候,普通的select 查詢操作無法提供足夠的數據保護。因為此時,其它事務tx可以對事務t查詢到數據進行修改或刪除。為了提供額外的安全機制,InnoDB提供了兩種類型的鎖定讀:
SELECT ... LOCK IN SHARE MODE(查看最新的數據快照)
對讀取到的行記錄加上共享鎖(s-lock)。此時,除非當前事務結束,否則,其它會話只可以讀取這些記錄,但是不能夠修改他們。 又如果,其它事務正在表記錄還沒有提交,那麽你的查詢會一直等到其它事務結束,然後使用最新的數據。
SELECT ... FOR UPDATE
會鎖定相關的索引記錄條目,就像對這些記錄條目發出update操作一樣。其它的事務將不能進行對這些記錄的更新,不能執行 select...lock in share mode ,也不能讀取這些數據。
一旦事務結束,locking reads加的鎖就會被釋放。
Example 1:(子表信息依賴於父表信息)
Select * from parent where name=’jones’ lock in share mode;
在鎖定讀狀態下獲取到父節點之後,便可以安全的執行子節點的插入,然後提交事務即可。在這期間其它事務只能讀父節點‘jones’,而修改操作只能等待...。
Example 2:(手動設置計數器字段)
問題:多個事務同事訪問到計數器的最大值x,然後最新添加的幾條記錄數量標記都是x+1而造成錯誤。
Select counter_field from t for update;
獲取最新的值,並對其獲取的每一行加上x-lock。此時其它事務,無法進行修改 或 讀取 操作。
然後執行計數器增加操作就不會出錯:
Update t set counter_field=counter_field+1;

4、Locks set by different sql statemetn in InnoDB

在使用不同的sql 語句執行命令是,命令會對所操作的表加上不同類型鎖。
不論where condition 有沒有記錄行的過濾操作,Locking read ,update,或者delete 通常會對sql掃描到的每一個索引記錄設置記錄鎖。InnoDB不會記住where 條件,但是會記住哪些索引範圍被表掃描過了。

InnoDB通常會使用next-key locks。
如果沒有適合的用於查詢的索引,那麽mysql必須要掃描全表,此時整張表的每一個記錄都會被加鎖。因此,設置、使用合適的索引,來避免很多不必要的表掃描十分重要。


在不同情況下,InnoDB是如何加鎖的呢?
1)select ... from
使用一致讀模式(consistent read mode),會讀取數據庫快照,而且在非serializable隔離級別的事務中不會給表加鎖。
當隔離級別為serializable時,查詢會把掃描到的索引記錄設置為 shared next-key locks(共享的下一鍵鎖)。
當使用唯一索引搜索唯一行時,Innodb只對掃描行加 index record lock。
2)select ... from ... lock in share mode
對掃描的所有index records加shared next-key locks。
當使用唯一索引搜索唯一行時,Innodb只對掃描行加 index record lock。

3)select ...from...for update
對掃描的所有index records加exclusive next-key locks。
當使用唯一索引搜索唯一行時,Innodb只對掃描行加 index record lock。
會阻止其它會話執行locking read。
4)update...where...
對掃描的所有index records加exclusive next-key locks。
當使用唯一索引搜索唯一行時,Innodb只對掃描行加 index record lock。
當update修改了聚集索引記錄的時候,二級索引記錄會被加上一個隱含的鎖。
5)Delete from ...where...
對掃描的所有index records加exclusive next-key locks。
當使用唯一索引搜索唯一行時,Innodb只對掃描行加 index record lock。
6)Insert
在插入行上設置exclusive lock(是一種索引記錄鎖,not next-key not gap key,因此不會阻止其它會話插入到之前的間隙)。
當插入到一條記錄會導致一個duplicate-key error並且已經被加了x-lock(重復鍵錯誤的時候),當前會話由請求exclusive鎖變成請求一個共享鎖。
7)insert...on duplicate key update
當發生重復鍵錯誤的時候,會設置排他鎖。如果是一個重復的主鍵,那麽它將會被加上exclusive index-record lock。如果是一個重復唯一鍵,那麽它將會被加上exclusive next-key lock。
8)Replace
如果沒有唯一鍵沖突,replace和insert的加鎖原則保持一致。
否則,會加exclusive next-key lock。
9)insert into t select ...from s where ...
為每個插入到表t的行加上exclusive index record lock (not gap lock)。
如果事務隔離級別為 read committed, 或者開啟innodb_locks_unsafe_for_binlog並且隔離級別不是serializable。那麽InnoDB會以一致性讀的方式來搜索表s。
否則,InnoDB對表s上的記錄設置shared next-key locks 。
10)如果一個表初始化了一個 auto_increment column
InnoDB使用一個 AUTO-INC 表鎖。加鎖持續到當前sql的結束,而不是事務的結束。當表持有AUTO-INC鎖時,其它會話不能向表裏插入數據。
11)foreign key
如果表存在外鍵約束。
12)lock tables
設置一個表鎖。但是它是比InnoDBb級別更高的mysql級別鎖。
當innodb_table_locsk=1(默認值)並且 autocommit=0時,引擎可以使用表鎖。
否則,INNODB的死鎖自動檢測無法檢查到與表鎖相關的死鎖。

5、Phantom Rows(幻讀行)
執行兩次select,但是第二次select出現了一行第一次select結果裏沒有出現的記錄。這一行數據,成為幻讀行。
InnoDB采用next-key locking算法來實現消除幻讀行的出現。
Create table a (id int,primary ke(id));
Insert into a values (1),(3),(6),(9);
Session 1:
Select * from a where id>9 for update;
sql對掃描記錄設置exclusive next-key lock。(6,9),(9,+無窮)兩個區間被加了排他鎖(gap lock),id=9的記錄被加了排他鎖(index record lock)。

6、InnoDB中的死鎖問題

死鎖就是不同事務之間持有對方需要的鎖,從而導致不同的事務無法繼續進行。因為兩個事務都在等待可用的資源,所以都不願意釋放自身所擁有的鎖。

死鎖舉例:

client a:                   client b:

mysql> CREATE TABLE t (i INT) ENGINE = InnoDB;

mysql> INSERT INTO t (i) VALUES(1);

mysql> START TRANSACTION;

mysql> SELECT * FROM t WHERE i = 1 LOCK IN SHARE MODE;

                                    mysql> delete from t where i=1; //操作進入隊列等待獲取x lock

mysql> delete from t where i=1;

+------+

| i |

+------+

| 1 |

+------+

因為client a持有的 s lock,還沒有被釋放,因此client b 要刪除i=1記錄的x lock無法立即獲取,進入隊列請求 x lock ,並進入等待狀態.......

然後,client a也要刪除i=1的記錄。此時,即發生了deadlock (出現了死鎖現象)

死鎖解釋:

a 需要 x lock來刪除行,但是因為 b已經請求了x lock,並且等待a 釋放s lock,因此 a 不能獲得 x lock。

因為b在a 之前請求了x lock,故a 持有的s lock也不能升級為 x lock。此時 InnoDB引擎會向其中的一個客戶端發送錯誤信息,並且釋放它的鎖。錯誤信息如下:

ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction

InnoDB鎖與事務模型