Innodb事務和鎖總結

Innodb事務和鎖總結

一. 事務隔離級別

  1. READ UNCOMMITED :讀未提交,事務之間可以看到彼此之間正在修改的內容,會出現所謂的髒讀現象
  2. READ COMMITED : 讀已提交,只能讀取已經提交的事務修改的數據,不會出現髒讀的現象,但會出現不可重複讀和幻讀的情況
  3. REPEATABLE READ : 可重複度,在innodb中解決了不可重複讀和幻讀的問題
  4. SERIALIZED : 串行化, 這個使用的比較少,隔離性最強,性能也最差

二. 鎖類型

S LOCK : 共享鎖 : S鎖與S鎖相互兼容,與X鎖不兼容
X LOCK :排他鎖 :與其他所有鎖類型不兼容,
簡單的可以理解爲讀寫鎖即可
意向鎖 (IS, IX) : 主要是用來表達加鎖的一種層次概念,比如希望在行級別加X鎖,那麼就需要在表級和頁級別加上IX鎖,意向鎖整體上兼容性會比較好,如果當前表中已經有了多個行級鎖,如果希望嘗試加表鎖,如果沒有意向鎖的話,那麼就需要取檢測當前行鎖的加鎖情況,但是有了意向鎖,就可以快速判斷當前嘗試加表鎖可能是需要等待的。

監控方法

  1. SHOW ENGINE INNODB STATUS 命令查看innodb引擎信息,其中包含當前執行事務的狀態和鎖的狀態
  2. 通過information_schema提供的表來排查可能存在的問題
    a. information_schema.innodb_trx
    b. information_schema.innodb_locks
    c. innodb_lock_waits

三.鎖是否的時機

lock通常在事務commit或是rollback之後纔會釋放

四. 一致性非鎖定讀

這是非常常見的問題,事務中會有大量的select操作,select操作通常情況下是不會對數據做加鎖操作的, 這樣保證了數據的併發性能,在不同的事務隔離級別下,非一致性鎖定讀的行爲是不太一樣的

  1. READ COMMITED : 讀取undo_log中該行最新的一條快照數據
  2. READ REPETABLE : 讀取undo_log中,最近的早於當前事務開啓時間的快照數據

五. 加鎖算法

上文描述了鎖的類型,即排它鎖,共享鎖,意向鎖之類,這裏所說的是加鎖的算法,即爲innodb使用了什麼樣的加鎖策略來處理實際場景中的併發問題。
3. record lock : 行鎖,對單個行記錄進行加鎖,行是innodb的最小數據管理單位,innodb中有專門的數據結構來存儲和管理行記錄,我們所有的查詢的數據最終都會落到存儲引擎的行上,具體行,頁,表的概念可以參考《Innodb存儲引擎》一書
2.gap lock : 間隙鎖,即對間隙加鎖,間隙其實就這裏只是針對間隙,不包括行(todo ,間隙的概念需要搞一下)
4. next-key lock : 對行和間隙和行都進行加鎖

netx-key lock算法主要解決的問題就是幻讀問題,因此這個算法只在 RR事務隔離級別中會使用

疑問? 不是MVCC可以在無鎖的情況下解決幻讀的問題嗎,爲什麼還要上next-key lock這麼重的算法呢?這裏就要引出兩個概念了,一致性非鎖定讀(快照讀) 和一致性鎖定讀(當前讀) 的概念了

一致性非鎖定讀 : 我們平時使用的select * from table 這種查詢語句,都是快照讀,在innodb RC和RR級別下都是通過MVCC機制來實現的,過程中是不加鎖的,讀取的數據未必是當前行中的真實的數據。

一致性鎖定讀 : select * from table for update 或者select * from table in shared mode 在這種情況下RC基本是會對對應的行加鎖,RR級別下就會使用next-key算法加鎖。

六、redo日誌

redo日誌是物理日誌,記錄的頁的物理修改日誌

七、undo日誌

undo日誌是邏輯日誌,記錄的是操作的sql邏輯,通常我們在修改數據的時候,先對行加鎖,然後將該版本的數據拷貝到undo日誌中,然後修改當前行的操作事務id,修改該行事務,然後把回滾指針指向und日誌。

七、MVCC

多版本控制(MVCC)在許多關係型數據庫中都有實現,innodb中主要是依賴undo日誌來實現,其中涉及到的一個比較重要的概念就是read veiw

low_limit_id :最大活躍事務id
up_limit_id : 最小活躍事務id
trx_ids : 活躍事務集合

如果當前trx_id > low_limit_id,那數據一定是不可見的,因爲數據是在當前事務開啓後才修改的
如果當前trx_id < up_limit_id,那麼數據一定是可見的,因爲事務在當前事務開啓前就已經提交了
如果trx_id 在這之間,那麼如果trx_id在trx_ids中,說明事務還沒有提交,那麼數據就不可見, 否則事務就是可見的。

無論是在什麼事務隔離級別下,基於read view的快照讀機制都相同的,只是創建read view的機制不太一樣。

在RR基本下,read view在事務開啓的時候就創建完成了,二在RC級別下,每次查詢都會重新創建read view。

如果一個當前事務爲事務A, 另外一個事務是事務B, 如果事務A開啓的時候,事務B已經提交了,那麼無論在RR級別或是RC級別下,B提交的數據對於事務A都是可見的; 如果B事務在A事務開啓之前就已經開啓了,但是A啓動的時候B事務還沒有提交,那麼先讓無論RR級別還是RC級別,數據在快照讀模式下也都是不可見的;如果B事務在A開啓之前就開啓,但是在A事務提交前B事務就提交了,此時對於RR級別而言,B的trx_id在trx_ids之中(trx_ids是在開啓事務的時候就已經初始化了),那麼RR級別下數據是可見的,但對於RC而言,重新生成的read view了,顯然數據就是可見的。

通過上述的分析,通過mvcc可以在RR級別下快照讀是完全解決了重複讀和幻讀的問題,RC級別還是會有不可重複讀和幻讀的問題,但是需要注意的是,MVCC只工作在快照讀(一致性非鎖定讀)條件下,對於當前讀的問題是無法走到MVCC的,所以一定要注意,MVCC只解決了快照讀的幻讀和不可重複讀問題。

這裏需要補充一個case來理解一下快照讀與當前讀的巨大區別

CREATE TABLE `user_info` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(10) NOT NULL DEFAULT '',
  `age` int(10) unsigned NOT NULL DEFAULT '0',
  PRIMARY KEY (`id`)
)
事務隔離級別爲RR

初始化數據

insert into user_info ('name', 'age') values ('jack', 1);

T1時刻A開啓事務,然後啓用快照讀

select * from user_info where id = 1;

查詢到的結果是

+----+------+-----+
| id | name | age |
+----+------+-----+
|  1 | tom  |   0 |
+----+------+-----+

T2時刻我們開啓事務B, 並且執行

update user_info set age =1 where id = 1;

此時事務A中執行當前讀

select * from user_info where id = 1 for update;

事務A將會被阻塞
此時提交事務B,事務A中顯示的查詢結果爲

+----+------+-----+
| id | name | age |
+----+------+-----+
|  1 | tom  |   1 |
+----+------+-----+

然後我們再次執行一次快照讀

select * from user_info where id = 1;

獲得的結果爲

+----+------+-----+
| id | name | age |
+----+------+-----+
|  1 | tom  |   0 |
+----+------+-----+

通過上述的例子,可以看到,快照讀和當前讀有着非常大的區別,在同一個事務當中,快照讀和當前讀返回的結果有可能是不一樣的。

我們看到,mvcc保證了快照讀每次讀取的數據是一致,next-key算法保證了當前讀是一致。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章