InnoDB——鎖、事務和複製

數據庫系統使用鎖是爲了支持對共享資源進行併發訪問,提供數據的完整性和一致性。

InnoDB存儲引擎中的鎖

  • 共享鎖(S Lock),允許事務讀一行數據
  • 排他鎖(X Lock),允許事務刪除或更新一行數據

兼容性:

  • S與S可以兼容
  • X不與任何鎖兼容

InnoDB支持多粒度鎖定,也就是允許行級和表級的鎖同時存在。實現方式爲通過意向鎖(Intention Lock):如果需要對最細粒度進行加鎖,需要在上層粒度加意向鎖。

具體舉例,如果需要對行加X鎖,需要對錶、頁依次加IX鎖。當意向鎖遇到等待時,必須等待結束後才能繼續對下級加鎖。如準備加對一行有S鎖的行加S鎖,行記錄因爲原來就有S鎖,所以表和頁都已經存在了IS鎖,首先新的IS鎖加在表上,因爲IS、IS鎖兼容,可以加上;然後再看頁鎖,同樣IS、IS兼容,可以加上;最後看行鎖IS與S兼容,那麼行記錄可以加上S鎖。對同樣這行有S鎖的行加X鎖,先加表IX鎖,IX與IS兼容,可以加上,頁同樣,最後IX鎖與行記錄上的S鎖不兼容,因此要等待S鎖釋放後才能加上X鎖。

一致性非鎖定讀

一致性非鎖定讀(consistent nonlocking read)是指InnoDB存儲引擎通過行多版本控制(multi version)的方式來讀取當前執行時間數據庫中行的數據。在行記錄正在執行DELETE或UPDATE時執行讀操作,不會等待鎖釋放,而是會去讀undo段中的行的快照數據。

在不同的事務隔離級別下,讀取方式不同,不是每個事務隔離級別都採用非鎖定的一致性讀,即使使用CNR,對快照數據的定義也不一樣。快照數據就是undo段中的歷史版本,一行記錄可能有多個版本,一般稱爲行多版本技術,由此帶來的併發控制,稱之爲多版本併發控制(Multi Version Concurrency Control,MVCC)。

在事務隔離級別READ COMMITTED下,非一致性讀總是讀取被鎖定行的最新一份快照數據,而在REPEATABLE READ事務隔離級別下,對於快照數據,非一致性讀總是讀取事務開始時的行數據版本。

一致性鎖定讀

REPEATABLE READ隔離級別下,InnoDB的SELECT操作使用一致性非鎖定讀,但支持兩種一致性鎖定讀操作:

  • SELECT…FOR UPDATE (X Lock)
  • SELECT…LOCK IN SHARE MODE (S Lock)

鎖的算法

InnoDB存儲引擎有3種行鎖的算法:

  • Record Lock:單個行記錄上的鎖
  • Gap Lock:間隙鎖,鎖定一個範圍,但不包括記錄本身
  • Next-Key Lock:Gap Lock+Record Lock,鎖定一個範圍和記錄本身

InnoDB對於行的查詢都是採用Next-Key Lock,例如索引有10,11,13,和20,那麼可鎖定的區間有:

  • (-∞, 10]
  • (10, 11]
  • (11, 13]
  • (13, 20]
  • (20, +∞)

Next-Key Lock的設計是爲了解決Phantom Problem。除了Next-Key Lock還有Previous-Key Lock,區別在於區間的開閉。

CREATE TABLE z(a INT,b INT,PRIMARY KEY(a),KEY(b));
INSERT INTO z SELECT 1,1;
INSERT INTO z SELECT 3,1;
INSERT INTO z SELECT 5,3;
INSERT INTO z SELECT 7,6;
INSERT INTO z SELECT 10,8;

現在z表中有如下數據:

mysql> SELECT * FROM z;
+----+------+
| a  | b    |
+----+------+
|  1 |    1 |
|  3 |    1 |
|  5 |    3 |
|  7 |    6 |
| 10 |    8 |
+----+------+

開啓一個事務,鎖定b=3行:

mysql> SELECT * FROM z WHERE b=3 FOR UPDATE;
+---+------+
| a | b    |
+---+------+
| 5 |    3 |
+---+------+
1 row in set (0.00 sec)

由於Next-Key Lock的存在,現在在輔助索引上3所處的區間被上鎖,也就是(1,3]。需要注意的是InnoDB還會對3的下一個區間加上gap lock,也就是(3,6)。那麼此時如果往這些區間內做其他操作會被阻塞:

mysql> INSERT INTO z SELECT 6,5;

在列a上由於是唯一索引列,Next-Key Lock會降級爲Record Lock,因此在索引a上的鎖定只針對a=5這一行。

Gap Lock的作用是爲了阻止多個事務將記錄插入到同一範圍內,這會導致Phantom Problem的產生。

InnoDB只在能夠定位到唯一行的情況下將Next-Key Lock降級爲Record Lock,也就是特別需要強調唯一索引由多個列組成的情況,查詢其中部分列仍會使用Next-Key Lock。

Phantom Problem是指在同一事務下,連續執行兩次SQL會導致不同的結果,第二次的SQL語句會返回之前不存在的行。

+---+
| a |
+---+
| 1 |
+---+
| 2 |
+---+
| 5 |
+---+
SELECT * FROM t WHERE a>2 FOR UPDATE;

Next-Key Locking這裏鎖住的不僅僅是a=5這個行,而是鎖定(2,+∞)這個範圍,因此此時如果使用的是REPEATABLE READ的話是無法向這個範圍內INSERT數據的,不會存在Phantom Problem。而如果是COMMITTED READ則允許寫入,例如下次執行的時候可能就會新增了一條a=4的記錄。

鎖問題

  • 髒讀,就是在讀到另一個事務中未提交的數據,違反數據庫的隔離性
  • 不可重複讀,事務內讀取同一數據集合,由於另一個事務的修改,事務兩次讀到的數據可能是不一樣的,違反了數據庫事務一致性的要求
  • 丟失更新,在事務中不使用SELECT…FOR UPDATE的查詢,在SELECT和UPDATE之間由其他事務進行了SELECT,在UPDATE COMMIT之後,其他事務也進行UPDATE(基於它自己的SELECT結果)和COMMIT,那麼就相當與地一個事務的UPDATE沒有其作用,需要操作串行化或者FOR UPDATE加鎖解決

死鎖

死鎖是指兩個或兩個以上的事務在執行過程中,因爭奪鎖資源而造成的一種互相等待的現象。

死鎖舉例:

  • A SELECT a=1 FOR UPDATE
  • B SELECT a=2 FOR UPDATE
  • A SELECT a=2 FOR UPDATE(阻塞,等待B釋放)
  • B SELECT a=1 FOR UPDATE(阻塞,等待A釋放)

在InnoDB中會話A會得到記錄爲2的這個資源,因爲B會被因死鎖而回滾。

鎖升級

Lock Escalation是指將當前鎖的粒度降低,如將1000個行鎖升級爲一個頁鎖。InnoDB不存在鎖升級問題。

事務

InnoDB中的事務完全符合ACID特性:

  • 原子性(atomicity)
  • 一致性(consistency)
  • 隔離性(isolation)
  • 持久性(durability)

通過在事務中使用SAVE WORK函數可以建立保存點。保存點可以通過ROLLBACK WORK: n來回滾。

事務的實現

事務的隔離性由上一章講的鎖來實現。原子性、一致性、持久性通過數據庫的redo log和undo log來完成。redo log稱爲重做日誌,用來保證事務的原子性和持久性;undo log用來保證事務的一致性。

redo log和undo log不是相互的逆過程,redo log是物理日誌,而undo log是操作的逆向操作,是邏輯日誌。

redo

redo log是用來實現事務的持久性,即ACID中的D,由內存中的redo log buffer和磁盤的redo log file組成。

當事務提交時,必須將所有日誌寫入重做日誌文件進行持久化,待事務的COMMIT操作完成纔算完成。重做日誌指的是redo log和undo log,redo log是用來保證事務的吃就行,undo log用來幫助事務回滾及MVCC的功能。redo log基本順序寫,而undo log是需要進行隨機讀寫的。

在每次將重做日誌緩衝寫入重做日誌文件後,InnoDB都調用一次fsync操作,確保日誌寫入重做日誌文件。磁盤性能決定了事務提交的性能。

在數據庫中還有一種二進制日誌(binlog),用來進行POINT-IN-TIME的恢復和主從複製環境的建立。redo log是在InnoDB存儲引擎層產生,bin log是在MySQL數據庫的上層產生,並且不只是對InnoDB引擎的;同時bin log是邏輯日誌,記錄的是對應的SQL語句,redo log是物理格式的日誌,記錄的是每個頁的修改;binlog只在事務提交完成後一次寫入,redo log在事務進行中不斷地被寫入,因此redo log不是隨事務提交的順序進行寫入的。

redo log是物理日誌,因此它是冪等的,而bin log由於是邏輯日誌,如INSERT等操作不是冪等的,所以它不能被重複執行。

undo

在對數據庫進行修改時,InnoDB存儲引擎不但會產生redo,還會產生一定量的undo。這樣如果用戶執行的事務或語句由於某種原因失敗了,又或者用戶用一條ROLLBACK語句請求回滾,就可以利用這些undo信息將數據回滾到修改之前的樣子。

與redo log放在文件不同,undo放在數據庫內部的一個特殊段中,稱爲undo段,位於共享表空間中。

undo是邏輯日誌,回滾時修改會被邏輯地取消,數據結構和頁本身在回滾之後可能不太相同,因爲這個過程中可能有其他併發的事務,因此不能將一個頁回滾到事務開始的樣子。InnoDB回滾時實際上是做與之前相反的工作,例如對於INSERT會回滾一個DELETE操作。

undo除了回滾以外的另一個作用是MVCC,若記錄被其他事務佔用,當前事務可以通過undo讀取之前的行版本信息,以此實現非鎖定讀。

undo log會產生redo log,因爲undo log也需要持久性的保護。

事務提交後不能馬上刪除undo log及undo log所在的頁,因爲可能還有其他事務需要通過undo log得到行記錄之前的版本。事務提交時會將undo log放入一個鏈表,是否可刪除由purge線程來判斷。

purge

DELETE FROM t WHERE a=1;

DELETE操作僅是將主鍵列中等於1的記錄delete flag設爲1,記錄還存在在B+樹上。purge用於最終完成delete和update操作,因爲MVCC所以記錄不能立即處理。若該行記錄已經不被其他任何事務引用,那麼就可以進行真正的DELETE操作。

group commit

事務非只讀的話,需要在提交時執行fsync操作,保證重做日誌都寫入磁盤。但是fsync性能是有限的,爲了提高效率,數據庫提供了group commit功能,一次fsync可以刷新確保多個事務日誌寫入文件。

BLGC是指:

  • Flush階段,將每個事務的bin log寫入內存
  • Sync階段,將內存的bin log刷到磁盤,若有多個事務,通過一次fsync完成bin log的寫入(BLGC)
  • Commit階段,leader根據順序調用存儲引擎層事務的提交

複製

複製是MySQL數據庫提供的一種高可用高性能的解決方案。因爲不是InnoDB實現,所以使用來傳遞數據的文件不是redo log而是bin log:

  • 主服務器將數據更改記錄到bin log中
  • 從服務器將bin log複製到自己的中繼日誌(relay log)中
  • 從服務器重做中繼日誌中的日誌,把更改應用到自己的數據庫上,達到數據的最終一致性

從服務器有兩個線程,一個是I/O線程,負責讀取主服務器的二進制日誌,並將其保存爲relay log,一個是SQL線程,負責執行relay log。

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