mysql中的事務隔離級別及可重覆讀讀提交詳細分析(mvcc多版本控制/undo log)

来源:https://www.cnblogs.com/eternityz/archive/2020/03/08/12443278.html
-Advertisement-
Play Games

一.事物隔離級別 讀未提交(read uncommitted)是指,一個事務還沒提交時,它做的變更就能被別的事務看到.通俗理解,別人改數據的事務尚未提交,我在我的事務中也能讀到。 讀提交(read committed)是指,一個事務提交之後,它做的變更才會被其他事務看到。通俗理解,別人改數據的事務已 ...


一.事物隔離級別

  • 讀未提交(read uncommitted)是指,一個事務還沒提交時,它做的變更就能被別的事務看到.通俗理解,別人改數據的事務尚未提交,我在我的事務中也能讀到。
  • 讀提交(read committed)是指,一個事務提交之後,它做的變更才會被其他事務看到。通俗理解,別人改數據的事務已經提交,我在我的事務中才能讀到。
  • 可重覆讀(repeatable read)是指,一個事務執行過程中看到的數據,總是跟這個事務在啟動時看到的數據 是一致的。當然在可重覆讀隔離級別下,未提交變更對其他事務也是不可見的。通俗理解,別人改數據的事務已經提交,我在我的事務中也不去讀。
  • 串列化(serializable ),顧名思義是對於同一行記錄,“寫”會加“寫鎖”,“讀”會加“讀鎖”。當出現讀寫鎖衝突的時候,後訪問的事務必須等前一個事務執行完成,才能繼續執行。通俗理解,我的事務尚未提交,別人就別想改數據。

圖片示例講解

  • 若隔離級別是“讀未提交”, 則 V1 的值就是 2。這時候事務 B 雖然還沒有提交,但是 結果已經被 A 看到了。因此,V2、V3 也都是 2。
  • 若隔離級別是“讀提交”,則 V1 是 1,V2 的值是 2。事務 B 的更新在提交後才能被 A 看到。所以, V3 的值也是 2。
  • 若隔離級別是“可重覆讀”,則 V1、V2 是 1,V3 是 2。之所以 V2 還是 1,遵循的就 是這個要求:事務在執行期間看到的數據前後必須是一致的。
  • 若隔離級別是“串列化”,則在事務 B 執行“將 1 改成 2”的時候,會被鎖住。直到事 務 A 提交後,事務 B 才可以繼續執行。所以從 A 的角度看, V1、V2 值是 1,V3 的值 是 2。
在實現上,資料庫裡面會創建一個視圖,訪問的時候以視圖的邏輯結果為準。在“可重覆 讀”隔離級別下,這個視圖是在事務啟動時創建的,整個事務存在期間都用這個視圖。 在“讀提交”隔離級別下,這個視圖是在每個 SQL 語句開始執行的時候創建的。這裡需要 註意的是,“讀未提交”隔離級別下直接返回記錄上的最新值,沒有視圖概念;而“串列 化”隔離級別下直接用加鎖的方式來避免並行訪問。

二.查看mysql的隔離級別

  • mysql> show variables like 'transaction_isolation';
  • mysql預設級別: repeatable read

三.事務隔離的實現

  • 每條記錄在更新的時候都會同時記錄一條回滾操作(也就是說redo log會記錄,undo log也會同時記錄).

  • 記錄上的最新值,通過回滾操作,都可以得到前一個狀態的值。

  • 回滾日誌刪除問題.

    在不需要的時候才刪除。也就是說,系統會判斷,當沒有事務再需要用到這些回滾日誌時,回滾日誌會被刪除。
    什麼時候才不需要了呢?就是當系統里沒有比這個回滾日誌更早的 read-view 的時候。
  • 回滾日誌存儲位置

    在 MySQL 5.5 及以前的版本,回滾日誌是跟數據字典一起放在 ibdata 文件里的(系統表空間),即使長 事務最終提交,回滾段被清理,文件也不會變小。我見過數據只有 20GB,而回滾段有 200GB 的庫。最終只好為了清理回滾段,重建整個庫。
  • 回滾流程

四.儘量不要使用長事務

  • 長事務意味著系統裡面會存在很老的事務視圖。由於這些事務隨時可能訪問資料庫裡面的任何數據,所以這個事務提交之前,資料庫裡面它可能用到的回滾記錄都必須保留,這就會導致大量占用存儲空間。

  • 還占用鎖資源,也可能拖垮整個庫

  • 詳解

    比如,在某個時刻(今天上午9:00)開啟了一個事務A(對於可重覆讀隔離級別,此時一個視圖read-view A也創建了),這是一個很長的事務……
    
    事務A在今天上午9:20的時候,查詢了一個記錄R1的一個欄位f1的值為1……
    
    今天上午9:25的時候,一個事務B(隨之而來的read-view B)也被開啟了,它更新了R1.f1的值為2(同時也創建了一個由2到1的回滾日誌),這是一個短事務,事務隨後就被commit了。
    
    今天上午9:30的時候,一個事務C(隨之而來的read-view C)也被開啟了,它更新了R1.f1的值為3(同時也創建了一個由3到2的回滾日誌),這是一個短事務,事務隨後就被commit了。
    
    ……
    
    到了下午3:00了,長事務A還沒有commit,為了保證事務在執行期間看到的數據在前後必須是一致的,那些老的事務視圖、回滾日誌就必須存在了(read-view B,read-view C),這就占用了大量的存儲空間。
    
    源於此,我們應該儘量不要使用長事務。

在information_schema 庫的 innodb_trx 這個表中查詢長事務

select * from information_schema.innodb_trx where TIME_TO_SEC(timediff(now(),trx_started))>60

五.事務的啟動方式

  • 顯式啟動事務語句

    begin 或 start transaction。
    配套的提交語句是 commit,
    回滾語句是 rollback。
  • set autocommit=0,會將線程的自動提交關掉.

    意味著如果你只執行一個 select 語句,這個事務就啟動了,而且並不會自動提交。這個事務持續存在直到你主 動執行 commit 或 rollback 語句,或者斷開連接。
  • select也是事物

六.可重覆讀隔離級詳細分析

可重覆讀隔離級別下的更新和讀取

mysql> CREATE TABLE `t` (
  `id` int(11) NOT NULL, 
  `k` int(11) DEFAULT NULL, 
  PRIMARY KEY (`id`)
) ENGINE=InnoDB;
insert into t(id, k) values(1,1),(2,2);

註意的是事務的啟動時機。

在可重覆讀RR隔離級別模式下,begin/start transaction 命令並不是一個事務的起點,在執行到它們之後的第一個操作 InnoDB 表的語句,事務才真正啟動。如果你想要馬上啟動一個事務,可以使用 start transaction with consistent snapshot 這個命令。

  • 第一種啟動方式,begin/start transaction一致性視圖是在第執行第一個快照讀語句時創建的;
  • 第二種啟動方式,start transaction with consistent snapshot一致性視圖是在執行 start transaction with consistent snapshot 時創建的。

上面圖1執行的結果是:

  • sessionB查詢到的k值為3
  • sessionA查詢到的k值為1
  • 執行順序是,先執行sessionC更新,在執行sessionB更新和查詢,再執行sessionA的查詢
  • 得到上面的執行結果的原因是什麼呢,下麵分析.

mysql中兩個視圖概念

  • view。它是一個用查詢語句定義的虛擬表,在調用的時候執行查詢語句並生成結 果。創建視圖的語法是 create view ... ,而它的查詢方法與表一樣。
  • InnoDB 在實現 MVCC 時用到的一致性讀視圖,即 consistent read view, 用於支持 RC(Read Committed,讀提交)和 RR(Repeatable Read,可重覆讀)隔 離級別的實現。它沒有物理結構,作用是事務執行期間用來定義“我能看到什麼數據”。

快照在MVCC里是怎麼工作的?

  • 在可重覆讀隔離級別下,事務在啟動的時候就“拍了個快照”。註意,這個快照是基於整庫的。

  • InnoDB 裡面每個事務有一個唯一的事務 ID,叫作 transaction id。它是在事務開始的時候向 InnoDB 的事務系統申請的,是按申請順序嚴格遞增的。

  • 每行數據也都是有多個版本的,涉及到transaction id

    • 每次事務更新數據的時候,都會生成一個新的數據版本,並且把 transaction id 賦值給這個數據版本的事務 ID,記為 row trx_id

    • 同時,舊的數據版本要保留,並且在新的數據版本中,能夠有信息可以直接拿到它。

    • 也就是說,數據表中的一行記錄,其實可能有多個版本 (row),每個版本有自己的 row trx_id。

    • 圖中虛線框里是同一行數據的 4 個版本,當前最新版本是 V4,k 的值是 22,它是被transaction id 為 25 的事務更新的,因此它的 row trx_id 也是 25。

    • 前面的文章不是說,語句更新會生成 undo log(回滾日誌)嗎?那麼,undo log 在哪呢?

    • 實際上,圖 2 中的三個虛線箭頭,就是 undo log;而 V1、V2、V3 並不是物理上真實存 在的,而是每次需要的時候根據當前版本和 undo log 計算出來的。比如,需要 V2 的時 候,就是通過 V4 依次執行 U3、U2 算出來。

  • 按照可重覆讀的定義,一個事務啟動的時候,能夠看到所有已經提交的事務結果。但是之後,這個事務執行期間,其他事務的更新對它不可見。

  • 一個事務只需要在啟動的時候聲明說,“以我啟動的時刻為準,如果一個數據版本是在我啟動之前生成的,就認;如果是我啟動以後才生成的,我就不認,我必須要找到它的上一個版本”。
    當然,如果“上一個版本”也不可見,那就得繼續往前找。還有,如果是這個事務自己更
    新的數據,它自己還是要認的。

  • 在實現上, InnoDB 為每個事務構造了一個數組,用來保存這個事務啟動瞬間,當前正 在“活躍”的所有事務 ID。“活躍”指的就是,啟動了但還沒提交。數組裡面事務 ID 的最小值記為低水位,當前系統裡面已經創建過的事務 ID 的最大值加 1 記為高水位。這個視圖數組和高水位,就組成了當前事務的一致性視圖(read-view)。

  • 當開啟事務時,需要保存活躍事務的數組(A),然後獲取高水位(B)兩者中間會不會產生新的事務?

    • A和B之間在事務系統的鎖保護下做的,可以認為是原子操作,期間不能創建事務。
    • 高水位不在視圖數組裡面,高水位應該就是屬於未來未開始事務了
    • 事務A啟動時,當前活躍事務數組包不包括自己的trx_id,因為如果是自己更新的,總是可見的
  • 數據版本的可見性規則,就是基於數據的 row trx_id 和這個一致性視圖的對比結果得到 的。這個視圖數組把所有的 row trx_id 分成了幾種不同的情況。

    • 對於當前事務的啟動瞬間來說,假設當前trx id為98 , 在當前事務開始後,計算活躍事務之前又產生了個新事務trx id為99沒有commit,假設活躍事務的id組成的數據為下麵的數組[80,88,99],此時事務80/88/99為活躍事務,99為當前系統中事務最大ID, 高水位100是當前系統最大事務id99加1計算出來的,則會有以下幾種可能:

      1. 如果落在綠色部分,表示這個版本是已提交的事務或者是當前事務自己生成的,這個數據是可見的; 即80以前的事務都可見

      2. 如果落在紅色部分,表示這個版本是由將來啟動的事務生成的,是肯定不可見的; 100及100以後的事務都不可見

      3. 如果落在黃色部分,那就包括兩種情況

        a. 若 row trx_id 在數組中,表示這個版本是由還沒提交的事務生成的,不可見; 80/88/99為活躍事務,不可見

        b. 若 row trx_id 不在數組中,表示這個版本是已經提交了的事務生成的,可見。80~99中間,去除80/88/99,比如81等其餘的是可見的.

  • InnoDB 利用了“所有數據都有多個版本”的這個特性,利用數據可見性規則實現了“秒級創建快照”的能力。

  • 為什麼會出現sessionB查詢到的k值為3,sessionA查詢到的k值為1呢,根據上面的數據可見性分析如下:

    • 這裡,我們不妨做如下假設:

      1. 事務 A 開始前,系統裡面只有一個活躍事務 ID 是 99;
      2. 事務 A、B、C 的版本號分別是 100、101、102,且當前系統里只有這四個事務;
      3. 三個事務開始前,(1,1)這一行數據的 row trx_id 是 90。
    • 事務 A 的視圖數組就是 [99,100], 事務 B 的視圖數組是 [99,100,101], 事務 C 的視 圖數組是 [99,100,101,102]。

      從圖中可以看到,第一個有效更新是事務 C,把數據從 (1,1) 改成了 (1,2)。這時候,這個數據的最新版本的 row trx_id 是 102,而 90 這個版本已經成為了歷史版本。
      
      第二個有效更新是事務 B,把數據從 (1,2) 改成了 (1,3)。這時候,這個數據的最新版本 (即 row trx_id)是 101,而 102 又成為了歷史版本。{備註:按理說事務B是[99,100,101],此時找到(1,2)的時候判斷出row trx_id=102,比它自己的高水位大,處於紅色區域,不可見,應該往前找,找(1,1)版本,但是此時它卻是找的(1,2)row trx_id=102的版本,這是什麼原因的,是因為更新都是“當前讀”(current read),當前讀這個概念下麵解釋}
      
      你可能註意到了,在事務 A 查詢的時候,其實事務 B 還沒有提交,但是它生成的 (1,3) 這 個版本已經變成當前版本了。但這個版本對事務 A 必須是不可見的,否則就變成臟讀了。
      
      好,現在事務 A 要來讀數據了,它的視圖數組是 [99,100]。當然了,讀數據都是從當前版本讀起的。所以,事務 A 查詢語句的讀數據流程是這樣的:
              找到 (1,3) 的時候,判斷出 row trx_id=101,比高水位大,處於紅色區域,不可見;
              接著,找到上一個歷史版本,一看 row trx_id=102,比高水位大,處於紅色區域,不可見;
              再往前找,終於找到了(1,1),它的 row trx_id=90,比低水位小,處於綠色區域,可見。
      
      這樣執行下來,雖然期間這一行數據被修改過,但是事務 A 不論在什麼時候查詢,看到這 行數據的結果都是一致的,所以我們稱之為一致性讀。
  • 上面的分析判斷規則是從代碼邏輯直接轉譯過來的,一個數據版本,對於一個事務視圖來說,除了自己的更新總是可見以外,有三種情況:

    1. 版本未提交,不可見;
    2. 版本已提交,但是是在視圖創建後提交的,不可見;
    3. 版本已提交,而且是在視圖創建前提交的,可見。

更新邏輯

事務 B 的 update 語句,如果按照一致性讀,好像結果不對 哦?事務 B 的視圖數組是先生成的,之後事務 C 才提交,不是應該看不見 (1,2) 嗎,怎麼能算出 (1,3) 來?

  • 是的,如果事務 B 在更新之前查詢一次數據,這個查詢返回的 k 的值確實是 1。
  • 但是,當它要去更新數據的時候,就不能再在歷史版本上更新了,否則事務 C 的更新就丟 失了。因此,事務 B 此時的 set k=k+1 是在(1,2)的基礎上進行的操作。
  • 這裡就用到了這樣一條規則: 更新數據都是先讀後寫的,而這個讀,只能讀當前的 值,稱為“當前讀”(current read)。
  • 因此,在更新的時候,當前讀拿到的數據是 (1,2),更新後生成了新版本的數據 (1,3),這 個新版本的 row trx_id 是 101。
  • 所以,在執行事務 B 查詢語句的時候,一看自己的版本號是 101,最新數據的版本號也是 101,是自己的更新,可以直接使用,所以查詢得到的 k 的值是 3。
  • 除了 update 語句外,select 語句如果加 鎖,也是當前讀。
    • mysql> select k from t where id=1 lock in share mode; 讀鎖(S 鎖,共用鎖)
    • mysql> select k from t where id=1 for update; 寫鎖(X 鎖,排他鎖)

假設事務 C 不是馬上提交的,而是變成了下麵的事務 C’

事務 C’的不同是,更新後並沒有馬上提交,在它提交前,事務 B 的更新語句先發起了。前面說過了,雖然事務 C’還沒提交,但是 (1,2) 這個版本也已經生成了,並且是當前的 最新版本。那麼,事務 B 的更新語句會怎麼處理呢?

  • 上一篇文章中提到的“兩階段鎖協議”就要上場了
  • 事務 C’ 沒提交,也 就是說 (1,2) 這個版本上的寫鎖還沒釋放。而事務 B 是當前讀,必須要讀最新版本,而且 必須加鎖,因此就被鎖住了,必須等到事務 C’釋放這個鎖,才能繼續它的當前讀。
  • 這樣一致性讀、當前讀和行鎖就串起來了.在一致性讀的環境下,事務C' 執行更新,此時C'沒有commit,事務B就開啟了,因為事務B要進行當前讀,獲取最新的信息,讀的時候要加鎖(讀完立馬更新),但是此時事務C'還沒有commit,鎖(行鎖)還沒釋放,所以事務B需要等待事務C'釋放鎖之後才能獲取鎖,然後才能執行當前讀,讀到事務B更新了的(1,2),既而更新為(1,3),同時因為(1,3)是事務B自身更新的,所以事務B在查詢id=1的值時,自然而然的就查到了k為3. 但是對於事務A來說,查詢的時候,因為事務C'和事務B的更新都是在事務A開始之後,所以對於事務A都不可見,所以事務A讀取到的值為1. 上面的分析同樣適用於事務A/B/C

可重覆讀隔離級別RR核心

  • 核心就是一致性讀(consistent read),正式因為一致性讀的原因,所以本事務開始之後,就算其他事務更新了相關的值,此時本事務還是能查到本事務開始之前的值,而不是其他事務更新後的值.
  • 讀提交的邏輯和可重覆讀的邏輯類似,它們最主要的區別是
    • 在可重覆讀隔離級別下,只需要在事務開始的時候創建一致性視圖,之後事務里的其他查詢都共用這個一致性視圖;
    • 在讀提交隔離級別下,每一個語句執行前都會重新算出一個新的視圖。

讀提交RC隔離級別

start transaction with consistent snapshot; 在都提交下與start transaction等效.

  • 事務 A 的查詢語句的視圖數組是在執行這個語句的時候創建的,時序上 (1,2)、(1,3) 的生成時間都在創建這個視圖數組的時刻之前。
  • 但是(1,3) 還沒提交,屬於情況 1,不可見; (1,2) 提交了,屬於情況 3,可見。
  • 所以,這時候事務 A 查詢語句返回的是 k=2。
  • 顯然地,事務 B 查詢結果 k=3。

站在巨人的肩膀上摘蘋果:

https://time.geekbang.org/column/intro/100020801


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 紅帽認證系統管理員(Red Hat Certified System Administrator):具備紅帽Linux環境所需的核心系統管理技能。 ...
  • Ingress控制器介紹 安裝部署traefik 創建traefik的web ui的ingress規則 ingress實驗 hostPath實驗 PV和PVC 研究的方向 重啟k8s二進位安裝(kubeadm)需要重啟組件 ...
  • k8s系統架構 從系統架構來看,k8s分為2個節點 Master 控制節點 指揮官 Node 工作節點 幹活的 1.Master節點組成 API Server :提供k8s API介面 主要處理Rest操作以及更新Etcd中的對象 是所有資源增刪改查的唯一入口。 Scheduler:資源調度器 根據 ...
  • ELk簡介 基本架構圖 傳統日誌分析需求(面試) 1.找出訪問網站頻次最高的 IP 排名前十 2.找出訪問網站排名前十的 URL 3.找出中午 10 點到 2 點之間 www 網站訪問頻次最高的 IP 4.對比昨天這個時間段和今天這個時間段訪問頻次有什麼變化 5.對比上周這個時間和今天這個時間的區別 ...
  • 周五(3月6號)筆者發現有台物理機上掛載了一個並非常用的nbd設備,估計是之前人做的測試留下來的,決定卸載它,順帶瞭解了下nbd的設備信息。 什麼是nbd 全稱是network block device,類似於nfs,遠程設備可以掛載,只不過掛載的不再是文件系統,而是塊設備。 nbd一般分為clie ...
  • 最近遇到這個問題,其實提示很簡單了。 去oracle 論壇看下,有人回覆了 Extract was off for two long. All redo logs have been switched. You have to provide archive logs to Oracle defau ...
  • 這篇文章主要來介紹下什麼是 Analysis ,什麼是分詞器,以及 ElasticSearch 自帶的分詞器是怎麼工作的,最後會介紹下中文分詞是怎麼做的。 首先來說下什麼是 Analysis: 什麼是 Analysis? 顧名思義,文本分析就是 把全文本轉換成一系列單詞(term/token)的過程 ...
  • MySQL 中是沒有 Oracle 的函數索引功能的,把 MySQL 的 Generated Column 稱為“函數索引”並不准確,但可以和函數索引達到同樣的效果,也有人把這個特性稱為“衍生列”。 Generated Column 是什麼 Generated Column 的值是根據其定義的表達式 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...