概念
InnoDB的MVCC(Multi-Version Concurrency Control)即多版本并發控制,是一種用于處理并發事務的機制。它通過保存數據在不同時間點的多個版本,讓不同事務在同一時刻可以看到不同版本的數據,以此來減少鎖競爭,提高數據庫的并發性能,同時保證事務的隔離性。
實現原理
隱藏列
InnoDB會為表中的每行記錄添加三個隱藏列:
- DB_TRX_ID:記錄最后一次對該行記錄進行插入或更新操作的事務ID。當進行刪除操作時,也會將其視為一次更新操作,只是會將該行記錄標記為已刪除。
- DB_ROLL_PTR:回滾指針,指向該行記錄的上一個版本所在的回滾段。通過這個指針,能找到該行記錄的歷史版本。
- DB_ROW_ID:如果表沒有主鍵,InnoDB會自動生成一個隱藏的自增主鍵。
回滾段(Rollback Segment)
回滾段用于存儲數據的舊版本。當一個事務對某行記錄進行更新或刪除操作時,InnoDB不會直接覆蓋原數據,而是將舊版本的數據復制到回滾段中,并更新當前行的DB_TRX_ID
和DB_ROLL_PTR
。這樣,就可以保留數據的歷史版本,供其他事務查看。
事務ID
每個事務在啟動時都會被分配一個唯一的事務ID,且這個ID是按照事務啟動的順序依次遞增的。
可見性判斷規則
在不同的事務隔離級別下,InnoDB根據事務的開始時間和記錄的隱藏列信息來判斷哪些版本的數據對當前事務是可見的。以下以可重復讀(REPEATABLE READ)隔離級別為例說明:
- 當事務開始時,會獲取一個當前系統中所有活躍事務ID的列表。
- 對于要查詢的每一行記錄:
- 如果該行記錄的
DB_TRX_ID
小于當前事務開始時活躍事務ID列表中的最小值,說明該行記錄是在當前事務開始之前就已經提交的事務修改的,當前事務可以看到這個版本的數據。 - 如果該行記錄的
DB_TRX_ID
大于當前事務開始時活躍事務ID列表中的最大值,說明該行記錄是在當前事務開始之后才開始的事務修改的,當前事務看不到這個版本的數據。 - 如果該行記錄的
DB_TRX_ID
在當前事務開始時活躍事務ID列表的范圍內,需要進一步判斷該事務是否已經提交。如果已經提交,當前事務可以看到這個版本的數據;如果未提交,當前事務看不到這個版本的數據。
- 如果該行記錄的
不同事務隔離級別下的MVCC表現
讀未提交(READ UNCOMMITTED)
此隔離級別下,MVCC基本不起作用。事務可以讀取到其他事務未提交的數據,不會考慮數據版本的可見性規則,可能會出現臟讀問題。
讀已提交(READ COMMITTED)
- 每次查詢時都會生成一個新的快照,即獲取當前最新的活躍事務ID列表。
- 事務只能看到已經提交的事務所做的更改,避免了臟讀問題,但可能會出現不可重復讀問題,因為在同一個事務中不同時間點的查詢可能會看到不同版本的數據。
可重復讀(REPEATABLE READ)
- 事務在開始時會生成一個快照,在整個事務期間都會使用這個快照。
- 同一個事務中多次讀取相同的數據,看到的版本始終是一致的,避免了不可重復讀問題,但可能會出現幻讀問題(不過InnoDB通過間隙鎖等機制在一定程度上解決了幻讀問題)。
串行化(SERIALIZABLE)
該隔離級別下,MVCC不發揮作用。事務會對讀取的數據加共享鎖,對寫入的數據加排他鎖,事務之間是串行執行的,避免了所有并發問題,但會導致并發性能極低。
MVCC的優點
- 提高并發性能:多個事務可以同時對數據進行讀寫操作,減少了鎖競爭,從而提高了數據庫的并發處理能力。
- 實現事務隔離:不同的事務可以看到不同版本的數據,從而實現了不同級別的事務隔離,保證了數據的一致性和完整性。
- 避免死鎖:由于減少了鎖的使用,降低了事務之間相互等待鎖的情況,減少了死鎖的發生概率。
MVCC的局限性
- 占用額外存儲空間:為了保存數據的多個版本,需要使用回滾段來存儲舊版本的數據,這會占用額外的存儲空間。
- 增加系統復雜度:MVCC的實現需要維護隱藏列、回滾段和事務ID等信息,增加了系統的復雜度和管理難度。
- 可能導致長事務問題:如果一個事務長時間不提交,會保留大量的舊版本數據,可能會導致回滾段空間不足,影響系統性能。