成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

全網最詳細MVCC講解,一篇看懂

開發 架構
MVCC 是一種強大的并發控制機制,在高并發環境中起著重要的作用。通過了解 MVCC 的原理和實現流程,我們可以更好地理解 MySQL 的并發控制機制,理解 MVCC 的原理對于接觸 MySQL 的開發人員來說是必不可少的知識點。
摘要

在當今高度并發的數據庫環境中,有效的并發控制是至關重要的。MVCC是MySQL中被廣泛采用的并發控制機制,它通過版本管理來實現事務的隔離性,允許讀寫操作同時進行,提高數據庫的并發性能和響應能力。

本文將深入解析MVCC機制的原理,幫助讀者更好地理解和應用這一關鍵技術。

MVCC 介紹

MVCC,全稱 Multi-Version Concurrency Control,即多版本并發控制

MVCC的目的主要是為了提高數據庫并發性能,用更好的方式去處理讀-寫沖突,做到即使有讀寫沖突時,也能做到不加鎖。

這里的多版本指的是數據庫中同時存在多個版本的數據,并不是整個數據庫的多個版本,而是某一條記錄的多個版本同時存在。

并發控制的挑戰

在數據庫系統中,同時執行的事務可能涉及相同的數據,因此需要一種機制來保證數據的一致性,傳統的鎖機制可以實現并發控制,但會導致阻塞和死鎖等問題。

MVCC的優點

MVCC機制具有以下優點:

提高并發性能:讀操作不會阻塞寫操作,寫操作也不會阻塞讀操作,有效地提高數據庫的并發性能。

降低死鎖風險:由于無需使用顯式鎖來進行并發控制,MVCC可以降低死鎖的風險。

當前讀和快照讀

在講解MVCC原理之前,我們先來了解一下,當前讀和快照讀。

當前讀

在MySQL中,當前讀是一種讀取數據的操作方式,它可以直接讀取最新的數據版本,讀取時還要保證其他并發事務不能修改當前記錄,會對讀取的記錄進行加鎖。MySQL提供了兩種實現當前讀的機制:

  • 一致性讀(Consistent Read):

默認隔離級別下(可重復讀),MySQL使用一致性讀來實現當前讀。

在事務開始時,MySQL會創建一個一致性視圖(Consistent View),該視圖反映了事務開始時刻數據庫的快照。

在事務執行期間,無論其他事務對數據進行了何種修改,事務始終使用一致性視圖來讀取數據。

這樣可以保證在同一個事務內多次查詢返回的結果是一致的,從而實現了當前讀。

  • 鎖定讀(Locking Read):
  • 鎖定讀是一種特殊情況下的當前讀方式,在某些場景下使用。
  • 當使用鎖定讀時,MySQL會在執行讀取操作前獲取共享鎖或排他鎖,以確保數據的一致性。
  • 共享鎖(Shared Lock)允許多個事務同時讀取同一數據,而排他鎖(Exclusive Lock)則阻止其他事務讀取或寫入該數據。
  • 鎖定讀適用于需要嚴格控制并發訪問的場景,但由于加鎖帶來的性能開銷較大,建議僅在必要時使用。

下面列舉的這些語法都是當前讀:

語法

SELECT ... LOCK IN SHARE MODE

SELECT ... FOR UPDATE

UPDATE

DELETE

INSERT

當前讀實際上是一種加鎖的操作,是悲觀鎖的實現。

快照讀

快照讀是在讀取數據時讀取一個一致性視圖中的數據,MySQL使用 MVCC 機制來支持快照讀。

具體而言,每個事務在開始時會創建一個一致性視圖(Consistent View),該視圖反映了事務開始時刻數據庫的快照。這個一致性視圖會記錄當前事務開始時已經提交的數據版本。

當執行查詢操作時,MySQL會根據事務的一致性視圖來決定可見的數據版本。只有那些在事務開始之前已經提交的數據版本才是可見的,未提交的數據或在事務開始后修改的數據則對當前事務不可見。

像不加鎖的 select 操作就是快照讀,即不加鎖的非阻塞讀。

快照讀可能讀到的并不一定是數據的最新版本,而有可能是之前的歷史版本。

注意:快照讀的前提是隔離級別不是串行級別,在串行級別下,事務之間完全串行執行,快照讀會退化為當前讀

MVCC主要就是為了實現讀-寫沖突不加鎖,而這個讀指的就是快照讀,是樂觀鎖的實現。

MVCC 原理解析

隱式字段

MySQL中的行數據,除了我們肉眼能看到的字段之外,其實還包含了一些隱藏字段,它們在內部使用,默認情況下不會顯示給用戶。

字段

含義

DB_ROW_ID

隱含的自增ID(隱藏主鍵),用于唯一標識表中的每一行數據,如果數據表沒有主鍵,InnoDB會自動以DB_ROW_ID產生一個聚簇索引。

DB_TRX_ID

該字段存儲了當前行數據所屬的事務ID。每個事務在數據庫中都有一個唯一的事務ID。通過 DB_TRX_ID 字段,可以追蹤行數據和事務的所屬關系。

DB_ROLL_PTR

該字段存儲了回滾指針(Roll Pointer),它指向用于回滾事務的Undo日志記錄。

Undo Log

上文提到了 Undo 日志,這個 Undo 日志是 MVCC 能夠得以實現的核心所在。

Undo日志(Undo Log)是MySQL中的一種重要的事務日志,Undo日志的作用主要有兩個方面:

  • 事務回滾:當事務需要回滾時,MySQL可以通過Undo日志中的舊值將數據還原到事務開始之前的狀態,保證了事務回滾的一致性。
  • MVCC實現:MVCC 是InnoDB存儲引擎的核心特性之一。通過使用Undo日志,MySQL可以為每個事務提供獨立的事務視圖,使得事務讀取數據時能看到一致且符合隔離級別要求的數據版本。

在InnoDB存儲引擎中,Undo日志分為兩種:插入(insert)Undo日志 和 更新(update)Undo日志

  • insert undo log:插入Undo日志是指在插入操作中生成的Undo日志。由于插入操作的記錄只對當前事務可見,對其他事務不可見,因此在事務提交后可以直接刪除,無需進行purge操作。
  • update undo log:更新Undo日志是指在更新或刪除操作中生成的Undo日志。更新Undo日志可能需要提供MVCC機制,因此不能在事務提交時就立即刪除。相反,它們會在提交時放入Undo日志鏈表中,并等待purge線程進行最終的刪除。刪除操作只是設置一下老記錄的 DELETED_BIT,并不真正將過時的記錄刪除,為了節省磁盤空間,InnoDB有專門的purge線程來清理 DELETED_BIT 為true的記錄。

注意:由于查詢操作(SELECT)并不會修改任何記錄,所以在查詢操作執行時,并不需要記錄相應的 undo log 。

不同事務或者相同事務對同一記錄行的修改,會使該記錄行的 undo log 成為一條鏈表,鏈首就是最新的記錄,鏈尾就是最早的舊記錄

舉個例子,比如有個事務A插入了一條新記錄:insert into user(id, name) values(1, "小明')

現在來了一個事務B對該記錄的name做出了修改,改為 "小王"。

在事務B修改該行數據時,數據庫會先對該行加排他鎖,然后把該行數據拷貝到 undo log 中作為舊記錄,即在 undo log 中有當前行的拷貝副本.

拷貝完畢后,修改該行name為 "小王,并且修改隱藏字段的事務ID為當前事務B的ID, 并將回滾指針指向拷貝到 undo log 的副本記錄,即表示我的上一個版本就是它,事務提交后,釋放鎖。

圖片圖片

此時又來了個事務C修改同一個記錄,將name修改為 "小紅"。

在事務C修改該行數據時,數據庫也先為該行加鎖,然后把該行數據拷貝到 undo log 中,作為舊記錄,發現該行記錄已經有 undo log 了,那么最新的舊數據作為鏈表的表頭,插在該行記錄的 undo log 最前面,如下圖:

圖片圖片

關于 DB_ROLL_PTR 與 Undo日志 的配合工作,具體流程如下:

  1. 在更新或刪除操作之前,MySQL會將舊值寫入Undo日志中。
  2. 當事務需要回滾時,MySQL會根據事務的Undo日志記錄,通過 DB_ROLL_PTR 找到對應的Undo日志。
  3. 根據Undo日志中記錄的舊值,MySQL將舊值恢復到相應的數據行中,實現數據的回滾操作。

比方說現在想回滾到事務B,name值為 "小王" 的時候,只需通過 DB_ROLL_PTR 順著列表找到對應的 Undo日志,將舊值恢復到數據行即可。

通過 DB_ROLL_PTR 和 Undo日志 的配合工作,MySQL能夠有效地管理事務的一致性和隔離性。Undo日志的使用也使得MySQL能夠支持MVCC,從而提供了高并發環境下的讀取一致性和事務隔離性。

版本鏈

在MVCC中,對于每次更新操作,舊值會被保存到一條undo日志中,即使它是該記錄的舊版本。隨著更新次數的增加,所有的版本都會通過roll_pointer屬性連接成一個鏈表,稱之為版本鏈。

版本鏈的頭節點代表當前記錄的最新值。此外,每個版本還包含生成該版本的事務ID。

Read View

一致性視圖,全稱 Read View ,是用來判斷版本鏈中的哪個版本對當前事務是可見的

Read View 說白了就是事務進行快照讀操作時候生成的讀視圖(Read View),在該事務執行快照讀的那一刻,會生成數據庫系統當前的一個快照,記錄并維護系統當前活躍事務的ID(每個事務開啟時,都會被分配一個ID,這個ID是遞增的)。

這里有一點要注意一下:Read View只針對 RC 和 RR級別

Read Uncommitted(RU)和 Serializable(串行化)是兩個特殊的隔離級別,它們不需要使用 Read View 的主要原因是:

  • Read Uncommitted(RU)隔離級別:在 RU 隔離級別下,事務可以讀取其他事務尚未提交的數據,即臟讀。這意味著不需要通過 Read View 來限制訪問范圍,事務可以自由地讀取其他事務的未提交數據。由于沒有對可見性進行嚴格控制,因此不需要創建或使用 Read View。
  • Serializable(串行化)隔離級別:在 Serializable 隔離級別下,事務具有最高的隔離性,確保每次讀取都能看到一致的快照。為了實現這種隔離級別,MySQL使用鎖機制來保證事務之間的串行執行。由于事務按順序執行,并且不允許并發操作,所以不需要使用 Read View 進行可見性判斷。

Read Uncommitted 和 Serializable 隔離級別下的事務規則不涉及基于 Read View 的可見性判斷。RU 允許臟讀,而 Serializable 則通過鎖機制保證串行執行。因此,在這兩個隔離級別下,不需要創建或使用 Read View。

Read View 可見性原則

Read View 遵循一個可見性原則,將要被修改的數據的 DB_TRX_ID 取出來,與系統當前其他活躍事務的ID去對比。

如果 DB_TRX_ID 跟 Read View 的屬性做了某些比較,不符合可見性,那就通過 DB_ROLL_PTR 回滾指針去取出 Undo Log 中的 DB_TRX_ID 再比較。

即遍歷鏈表的 DB_TRX_ID (從鏈首到鏈尾,即從最近的一次修改查起),直到找到滿足特定條件的 DB_TRX_ID,那么這個 DB_TRX_ID 所在的記錄就是當前事務能看見的最新老版本。

Read View 會維護以下幾個字段:

字段

含義

m_ids

Read View 創建時其他未提交的活躍事務 ID 列表。創建 Read View時,將當前未提交事務 ID 記錄下來,后續即使它們修改了記錄行的值,對于當前事務也是不可見的。m_ids 不包括當前事務自己和已提交的事務(正在內存中)。

m_creator_trx_id

創建該 Read View 的事務 ID。

m_low_limit_id

目前出現過的最大的事務 ID+1,即下一個將被分配的事務 ID。大于等于這個 ID 的數據版本均不可見。

m_up_limit_id

活躍事務列表 m_ids 中最小的事務 ID,如果 m_ids 為空,則  m_low_limit_idm_up_limit_id 。小于這個 ID 的數據版本均可見。

Read View 可見性具體判斷如下:

  1. 如果被訪問版本的 DB_TRX_ID 屬性值與 Read View 中的 m_creator_trx_id 值相同,表示當前事務正在訪問自己所修改的記錄,因此該版本可以被當前事務訪問。
  2. 如果被訪問版本的 DB_TRX_ID 屬性值小于 Read View 中的 m_up_limit_id 值,說明生成該版本的事務在當前事務生成 Read View 之前已經提交,因此該版本可以被當前事務訪問。
  3. 如果被訪問版本的 DB_TRX_ID 屬性值大于或等于 Read View 中的  m_low_limit_id 值,說明生成該版本的事務在當前事務生成 Read View 之后才提交,因此該版本不能被當前事務訪問。
  4. 如果被訪問版本的 DB_TRX_ID 屬性值位于 Read View 的  m_up_limit_id 和  m_low_limit_id 之間(包括邊界),則需要進一步檢查 DB_TRX_ID 是否在m_ids 列表中。如果在列表中,說明在創建ReadView時生成該版本的事務仍處于活躍狀態,因此該版本不能被訪問;如果不在列表中,說明在創建 Read View 時生成該版本的事務已經提交,因此該版本可以被訪問。

事務可見性示意圖:

圖片圖片

RC 和 RR 下的 Read View

RC 和 RR 下生成 Read View 的時機是有所差異的:

  • RC:每次 SELECT 數據前都生成一個ReadView。
  • RR:只在第一次讀取數據時生成一個ReadView,后面會復用第一次生成的。

正因為RC 和 RR生成 Read View 的時機不同,導致兩個級別下看到的數據會不一致。

舉例說明,假設數據初始狀態如下:

有 A,B,C 三個事務,執行順序如下:


事務A(事務ID: 100)

事務B(事務ID: 200)

事務C(事務ID: 300)

T1

begin



T2


begin

begin

T3

update user set name="小王" where id=1



T4

update user set name="小紅" where id=1


select * from user where id = 1

T5

commit

update user set name="小黑" where id=1


T6


update user set name="小白" where id=1

select * from user where id = 1

T7


commit


T8



select * from user where id = 1

T9



commit

T10




RC 下的 Read View

T4時刻

我們來看 T4 時刻的情況,此時 事務A 和 事務B 都還沒提交,所以活躍的事務ID,即 m_ids 為:[100,200],四個字段的值分別如下:

字段

m_ids

[100,200]

m_creator_trx_id

300

m_low_limit_id

400

m_up_limit_id

100

T4時刻的版本鏈如下:

圖片圖片

依據我們之前說的可見性原則,事務C最終看到的應該是  name = "小明"  的數據,理由如下:

最新記錄的 DB_TRX_ID 為 100,既不小于 m_up_limit_id,也不大于 m_low_limit_id,也不等于 m_creator_trx_id。

落在了黃區:

圖片圖片

DB_TRX_ID 存在于 m_ids 列表中,故不可見,順著版本鏈繼續往下。

根據 DB_ROLL_PTR 找到 undo log 中的前一版本記錄,前一條記錄的 DB_TRX_ID 還是 100,還是不可見,繼續往下。

繼續找前一條 DB_TRX_ID為 1,滿足 1 < m_up_limit_id,可見,所以事務C 查詢到數據為  name = "小明"  。

T6時刻

T6時候的版本鏈如下:

圖片圖片

T6時刻,會再次生成新的 Read View,四個字段的值分別如下:

字段

m_ids

[200]

m_creator_trx_id

300

m_low_limit_id

400

m_up_limit_id

200

根據可見性原則,最終T6時刻事務C 查詢到數據為  name = "小紅" 。

T8時刻

T8時刻的版本鏈和T6時刻是一致的,不同的是 Read View,因為T8時刻會再生成一個 Read View,四個字段的值分別如下:

字段

m_ids

[]

m_creator_trx_id

300

m_low_limit_id

400

m_up_limit_id

400

根據可見性原則,最終T8時刻事務C 查詢到數據為  name = "小白" 。

總結一下,事務C在 RC 級別下各個時刻看到的數據如下:

時刻

name

T4

小明

T6

小紅

T8

小白

下面我們來看看,RR 級別下的表現是如何的。

RR 下的 Read View

(RR 的版本鏈和 RC 的版本鏈是一致的,區別在于 Read View)

T4時刻

T4 時刻的情況,和 R C的情況是一致的:

字段

m_ids

[100,200]

m_creator_trx_id

300

m_low_limit_id

400

m_up_limit_id

100

根據可見性原則,最終T4時刻事務C 查詢到數據為  name = "小明" ,和 RC 的T4時刻是一致的。

T6時刻

RR 級別會復用 Read View,所以T6時刻也是:

字段

m_ids

[100,200]

m_creator_trx_id

300

m_low_limit_id

400

m_up_limit_id

100

根據可見性原則,T6時刻我們發現事務C查詢到的數據還是  name = "小明" 。

繼續看T8時刻。

T8時刻

T8時刻繼續復用先前的 Read View。

根據可見性原則,T8時刻事務C查詢到的數據依舊是   name = "小明" 。

小結

我們將事務C在 RC 和 RR 級別下看到的數據,放到一塊來對比下:

時刻

RC

RR

T4

小明

小明

T6

小紅

小明

T8

小白

小明

可以看出二者由于生成 Read View 的時機不同,導致在各個時刻看到的數據會存在差異。

回過頭來看 RC 和 RR 隔離級別的定義,會有種恍然大悟的感覺:

  • 讀已提交(Read Committed):事務只能讀取到已經提交的數據。
  • 可重復讀(Repeatable Read):事務在整個事務期間保持一致的快照視圖,不受其他事務的影響。

總之在 RC 隔離級別下,每個快照讀都會生成并獲取最新的 Read View;而在 RR 隔離級別下,則是只在第一個快照讀創建Read View,之后的快照讀獲取的都是同一個Read View

RR 級別下能否防止幻讀

嚴謹的說,RR 級別下只能防止部分幻讀

首先,幻讀通常指的是在同一個事務中,第二次查詢發現了新增加的行,而第一次查詢并沒有返回這些新增加的行。

通過前面的例子,我們也看到了,在 RR 隔離級別下,由于一致性視圖的存在,如果其他事務插入了新的行,在同一個事務中進行多次查詢,這些新增的行將會被包含在事務的一致性視圖中,確實可以避免部分幻讀場景。

這里注意一下:MVCC解決的只是 RR 級別下快照讀的幻讀問題,而當前讀的幻讀問題則是通過臨鍵鎖來解決的。也就是說 RR 級別下是通過 MVCC+臨鍵鎖 來解決大部分幻讀問題的。

為什么說是部分解決?看下面這個例子:


事務A

事務B

T1

begin


T2


begin

T3


select * from user

T4

insert into user(id, name) values(2, "小張')


T5


select * from user for update

T6

commit


T7


commit

假設數據初始狀態如下:

圖片圖片

T3時刻看到的數據只有一條 name = "小明",而T5時刻,由于 select * from user for update 使用的是當前讀,讀取的是最新的數據版本,T5時刻查詢出來的數據是兩條,name 分別為 "小明" 和 "小張"。

理解了上面的例子之后,再看下面這個例子:


事務A

事務B

T1

begin


T2


begin

T3


select * from user

T4

insert into user(id, name) values(2, "小張')


T5


update user set name="小陳" where id=2

T6


select * from user

T7

commit


T8


commit

UPDATE 語句也是當前讀,也會發生幻讀問題,最終看到的數據是name 分別為 "小明" 和 "小陳"。

這里發生幻讀的原因,和上面的例子是一樣的,本質都是在一個事務中,即使用了快照讀又使用了當前讀,RR 級別下無法預防此種情況,所以說 RR 級別下無法完全解決幻讀問題。

總結

綜上所述,MVCC 是一種強大的并發控制機制,在高并發環境中起著重要的作用。通過了解 MVCC 的原理和實現流程,我們可以更好地理解 MySQL 的并發控制機制,理解 MVCC 的原理對于接觸 MySQL 的開發人員來說是必不可少的知識點。

責任編輯:武曉燕 來源: Java隨想錄
相關推薦

2022-10-26 07:39:36

MVCC數據庫RR

2021-05-27 05:24:21

云計算數據網絡

2019-04-17 15:16:00

Sparkshuffle算法

2021-04-09 08:40:51

網絡保險網絡安全網絡風險

2024-06-25 08:18:55

2024-07-02 08:36:09

JavaScriptUnicode模式

2014-08-08 15:22:20

2024-02-22 17:15:22

JS垃圾回收機制

2017-06-06 13:35:59

AndroidToolbar

2021-10-11 11:08:33

HDFS快照系統

2023-11-30 08:32:31

OpenFeign工具

2024-02-07 08:22:36

2015-11-12 10:40:43

2019-06-25 10:53:06

AndroidFlutter通信

2022-07-19 19:39:05

RTK技術定位技術

2019-07-15 09:30:26

服務協議IP 地址

2021-09-05 07:55:36

Lsm核心實現

2020-11-20 10:15:05

TensorFlow

2020-04-14 20:40:58

Git內部存儲

2022-05-29 09:14:22

Web主頁WCAG
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 欧美日韩综合视频 | 欧美h | 不卡视频一区二区三区 | 秋霞电影一区二区三区 | 免费观看一级特黄欧美大片 | 国产精品久久精品 | 91精品久久久 | 国产精品久久久久久久久久久久冷 | 色噜噜狠狠色综合中国 | 国产精品一区二区福利视频 | 一区二区不卡高清 | 亚洲精品乱码 | 看片一区 | 亚洲激情在线观看 | 国产精品无 | 狠狠婷婷综合久久久久久妖精 | 九色视频网站 | 成人在线观看网址 | 天天澡天天狠天天天做 | 国产一区二区三区四区五区加勒比 | 男女羞羞的网站 | 亚洲精品视频一区二区三区 | 在线观看视频一区二区三区 | av中文字幕在线观看 | 狠狠爱网址 | 成人免费视频一区 | 亚洲综合一区二区三区 | 一级网站 | 在线欧美亚洲 | 日本成人久久 | 久久久免费电影 | 亚洲成人精品一区 | 欧美日韩高清免费 | 国产伦一区二区三区 | 香蕉一区二区 | 天天射影院 | 九九综合| 最新中文字幕在线 | 欧美精品网站 | 久草免费电影 | av电影手机版 |