MySQL InnoDB事務acid特性的原理和隔離級別的實現原理

InnoDB存儲引擎

InnoDB存儲結構

在這里插入圖片描述

表空間

則每張表都會有一個表空間(xxx.ibd),一個mysql實例可以對應多個表空間

  • 系統表空間
    • 存儲數據字典(表結構定義、索引信息等)、Change Buffer、Doublewrite Buffer
    • undo log,默認在此可更改到獨立表空間
    • 默認存儲在ibdata1文件中
  • 獨立表空間
    • 每個表單獨對應一個.ibd文件(存儲表數據和索引)
  • 通用表空間
    • 存儲多個表的數據和索引
  • 臨時表空間
    • 臨時表數據CREATE TEMPORARY TABLE
    • 排序和聚合操作的臨時數據ORDER BYGROUP BY
    • JOIN多表連接的臨時數據
  • Undo 表空間
    • 存儲 Undo Log(默認位于系統表空間,可分離)

  • 數據段:B+樹葉子節點
  • 索引段:B+樹非葉子結點
  • 回滾段:管理undo log

  • 連續分配的最小單元(1區 = 64個連續頁 = 1MB也就是)
  • 作用:減少隨機 I/O(預分配連續空間),避免大量小頁零散分布

  • 磁盤IO最小單元(默認 16KB

  • InnoDB 存儲引擎數據是按行進行存放的

InnoDB的內存架構

核心組件

InnoDB 內存架構
緩沖池 Buffer Pool
日志緩沖區 Log Buffer
自適應哈希索引 AHI
更改緩沖區 Change Buffer
緩沖池Buffer Pool
  • 作用:緩存磁盤數據頁,減少磁盤IO操作
  • LRU算法(最近最少使用)
新數據頁
插入到舊子列表頭部
停留時間 > 1s?
移動到新子列表頭部
保持原位
頻繁訪問頁保持在前端
短期訪問頁快速淘汰
  • 分區管理
    • 新子列表 (37%):頻繁訪問的熱數據
    • 舊子列表 (63%):新加載的冷數據
  • 在專用服務器上,通常將多達**80%**的物理內存分配給緩沖池
日志緩沖區Log Buffer
  • 用來保存要寫入到磁盤中的log日志數據(redo log 、undo log), 默認大小為 16MB,日志緩沖區的日志會定期刷新到磁盤中。如果需要更新、插入或刪除許多行的事 務,增加日志緩沖區的大小可以減少磁盤 I/O。
  • 參數:
    • innodb_log_buffer_size:緩沖區大小
    • innodb_flush_log_at_trx_commit:日志刷新到磁盤時機,取值主要包含以下三個:
      • 1: 日志在每次事務提交時寫入并刷新到磁盤,默認值。
      • 0: 每秒將日志寫入并刷新到磁盤一次。
      • 2: 日志在每次事務提交后寫入,并每秒刷新到磁盤一次。
更改緩沖區Change Buffer

針對非唯一二級索引,在執行DML語句時,如果這些語句不在Buffer Pool中,不會直接操作磁盤進行修改,而是先將數據變更存在Change Buffer中,在未來數據讀取時,將數據合并到Buffer Pool中,再將合并后的數據刷新到磁盤中。

自適應哈希索引

自適應hash索引,用于優化對Buffer Pool數據的查詢。MySQL的innoDB引擎中雖然沒有直接支持 hash索引,但是給我們提供了一個功能就是這個自適應hash索引。因為前面我們講到過,hash索引在 進行等值匹配時,一般性能是要高于B+樹的,因為hash索引一般只需要一次IO即可,而B+樹,可能需 要幾次匹配,所以hash索引的效率要高,但是hash索引又不適合做范圍查詢、模糊匹配等。 InnoDB存儲引擎會監控對表上各索引頁的查詢,如果觀察到在特定的條件下hash索引可以提升速度, 則建立hash索引,稱之為自適應hash索引。

MVCC

多版本并發控制,是數據庫實現高并發訪問的核心技術,維護一個數據的多個版本,使得MySQL能在RR和RC級別不使用鎖機制的情況下實現非阻塞讀,同時保證事務的隔離性。

RU讀取最新的數據版本,除事務回滾用到undo log不涉及MVCC快照讀。

S將所有讀操作隱式轉換為當前讀(FOR SHARE),同樣不涉及快照讀。

MVCC 核心組成

組件作用
Undo Log存儲數據歷史版本鏈
----------------
Read View事務開啟時生成的"數據可見性快照"
-----------------
表中隱藏列記錄事務版本信息
DB_TRX_ID最近修改/插入該數據的事務ID,最后一次修改該記錄的事務ID
DB_ROLL_PTR指向 Undo Log 的指針(用于回溯歷史版本),指向上一個版本

undo log

回滾日志,是一種邏輯日志但記錄的數據修改前的物理行數據值。是InnoDB引擎中實現事務原子性一致性MVCC的重要機制。記錄事務對數據的修改操作,用于事務回滾時提供撤銷修改的數據依據,或在快照讀時提供歷史版本數據。

  • undo log類型
    • Insert undo log(插入回滾日志):僅用于記錄 INSERT 操作。
      • 記錄內容:插入的完整行數據(包括所有字段值)。
      • 原因:插入的記錄在事務提交前,僅對當前事務可見,其他事務無法訪問。若事務回滾,只需通過 undo log 定位到這些插入的行,直接刪除即可(反向操作是 “刪除插入的行”,而 undo log 記錄行數據是為了精準定位要刪除的記錄)。
    • Update undo log(更新回滾日志):用于記錄 UPDATEDELETE 操作(注:InnoDB 中 DELETE 本質是標記刪除,也屬于特殊的更新)。
      • 記錄內容:被修改行的舊版本數據(包括所有字段值),而非抽象的 “反向操作邏輯”。
      • 原因:更新 / 刪除操作會改變行的已有數據,回滾時需要恢復到修改前的狀態。例如,若將 age=20 改為 age=30,undo log 會記錄 age=20(舊值)以及未修改的字段數據,回滾時直接用舊值覆蓋新值即可;若刪除一行,undo log 會記錄該行刪除前的完整數據,回滾時重新插入該數據(恢復刪除)。
  • 存儲方式
    • 存儲在 InnoDB 的undo 表空間

    • 按 “段”管理,每個事務會分配一個或多個 undo log 段。

核心作用
  • 事務回滾

    • 當事務回滾ROLLBACK或數據庫崩潰,InnoDB通過undo log實現對數據修改的撤銷,恢復到事務開始時的狀態。
    • 示例
    BEGIN;
    UPDATE users SET balance = 100 WHERE id = 1;  -- 記錄undo log(舊值balance=50)
    DELETE FROM orders WHERE id = 10;  -- 記錄undo log(舊記錄完整信息)
    ROLLBACK;  -- 執行undo log:balance恢復為50,orders表恢復id=10的記錄
    
  • MVCC支持

    • 快照讀(普通SELECT)時,InnoDB通過undo log獲取數據的歷史版本,確保事務執行過程中看到的是事務開始時的一致性視圖,不會受其他事務影響,避免了臟讀、不可重復讀、幻讀。
事務回滾支持
  • 事務開始:分配undo log空間。
  • 修改操作:每次執行寫操作,將舊的數據版本寫入undo log。
    • 例如:UPDATE t SET a=2 WHERE id=1(原 a=1),undo log 記錄(id=1, a=1)
  • 事務提交
    • INSERT undo log:直接標記為可刪除。
    • UPDATE/DELETE undo log:保留,供其他事務的快照讀使用,由 purge 線程后續清理。
  • 事務回滾:反向執行 undo log 中的操作(如將 a=2 恢復為 a=1), v 徹底撤銷事務影響。
MVCC支持
  • 配合每行數據隱藏列DB_TRX_ID(記錄最后一次修改的事務ID)和DB_ROLL_PTR(指向undo log的指針)。
  • 當事務需要獲取對應的數據版本時,通過DB_ROLL_PTR遍歷undo log獲取符合當前事務可見性的版本。
版本鏈

版本鏈是快照讀(普通SELECT)實現一致性視圖的核心。

版本鏈的每個節點對應事務對某行的一次修改,而非一個事務的多次修改。版本鏈是通過行記錄的 roll_ptr 指針和 undo log 記錄的 prev 指針串聯形成的,每一次修改都會生成一個新的 undo log 節點。

示例

一張表的原始數據為:

idagenameDB_TRX_IDDB_ROLL_PTR
3030A301null

四個并發事務同時訪問這張表

事務2事務3事務4事務5
開始事務開始事務開始事務開始事務
修改age=3(id=30)查詢id=30的記錄
提交事務
修改name=A3(id=30)
查詢id=30的記錄
提交事務
修改age=10(id=30)
查詢id=30的記錄
查詢id=30的記錄
提交事務

當事務2執行修改時,創建最新的版本(age=3),舊數據會記錄在undo log日志,形成下圖版本鏈:

在這里插入圖片描述

當事務3執行修改操作時,創建新的版本(name=A3),舊數據(age=3,非整行數據)會記錄在undo log,新版本DB_ROLL_PTR指向修改前舊版本:

在這里插入圖片描述

當事務3執行修改操作時,創建新的數據版本,舊數據(age=3)記錄在undo log,新數據版本DB_ROLL_PTR指向修改前的舊版本:

在這里插入圖片描述

不同事務或相同事務對同一記錄進行修改,會導致該記錄的undo log形成一條不同版本的版本鏈表,鏈表頭部是最新的數據版本,尾部是最早的數據版本。

Read View

一致性視圖,在事務開始時創建,記錄了事務啟動時活躍事務狀態。通過比對Read View中的參數和undo log中數據版本的事務ID,可以判斷事務在某時間點能看到的數據版本范圍,是事務內一致性讀的關鍵。

Read View的組成

Read View包含四個核心字段

字段含義
m_ids當前活躍的事務ID集合
min_trx_id最小活躍事務ID
max_trx_id即將分配的事務ID,即當前最大事務ID+1
creator_trx_id創建當前Read View的事務ID

活躍事務:指Read View創建時還未提交的事務。

創建Read View的時機

不同的隔離級別下創建Read View的時機也不同:

  • RC:在事務每次快照讀時創建。
  • RR:在事務第一次快照讀時創建,后續快照讀復用當前Read View。
判斷可見性

當事務訪問某一行數據時,會遍歷其undo log版本鏈,找到該事務可見的數據版本,trx_id是undo log版本鏈中的DB_trx_id(創建該版本的事務ID)。

判斷規則

條件是否可見說明
trx_id == creator_trx_id可見該版本是當前事務自己修改的
trx_id < min_trx_id可見該版本在Read View創建前就已提交
trx_id >= max_trx_id不可見該版本在Read View創建后才創建
trx_id ∈ m_ids不可見該版本由Read View創建時未提交的事務修改
trx_id ? m_idsmin_trx_id ≤ trx_id < max_trx_id可見該版本在Read View創建時已提交

隔離級別的實現原理

事務隔離級別的實現是MVCC和鎖機制配合的結果。

涉及到的核心機制

機制作用適用的隔離級別
MVCC(undo log+ReadView)實現非阻塞讀快照讀),通過版本鏈提供一致性視圖RC,RR
臨鍵鎖(間隙鎖+記錄鎖)鎖定索引間隙和記錄,防止插入和修改,解決幻讀臟寫RR,S
間隙鎖鎖定索引間隙,防止插入,避免幻讀RR,S
行鎖(記錄鎖)鎖定單行索引記錄,避免寫沖突(臟寫)所有寫操作
undolog事務回滾
讀未提交RU

核心特性:直接讀取最新的數據(包括未提交的數據變化)臟讀,所以RU的實現不依賴ReadView

  • undo log的表現
    • 讀操作直接訪問最新的數據版本(包括未提交的修改)。
    • undo log僅用于事務回滾。
  • 鎖機制
    • 寫操作加排它鎖(X鎖),持鎖至事務結束,避免臟寫
      • 不會阻止該行的讀操作,讀操作不會加鎖,排它鎖只阻塞嘗試獲取鎖的操作。
    • 讀操作(包括當前讀)不加鎖,導致臟讀
讀已提交RC

核心特性:避免臟讀、不可重復讀問題、幻讀問題、讀已提交的最新數據版本。

  • MVCC:
    • 每次快照讀創建新的ReadView,保證每次讀取的都是最新的已提交版本,快照讀在undo log版本鏈找到事務可見的數據版本(當前快照讀時最新已提交的數據版本)。
    • 因為使用ReadView,利用ReadView中關于ReadView創建時的參數(m_ids等)與undolog版本鏈的事務ID參數(DB_trx_ID)比對,能避免讀到活躍事務修改的數據版本,以此避免臟讀問題。
    • 會因為每次快照讀都創建新的ReadView,每個Readview可見的數據版本可能不同,造成不可重復讀的問題。
  • 鎖機制
    • 當前讀加記錄鎖,持鎖至事務結束,鎖定當前行,避免其他事物修改該行數據,造成臟寫
    • 不加間隙鎖,會出現幻讀
可重復讀RR
  • MVCC:

    • 第一次快照讀時創建ReadView,該事務內所有快照讀會在共用該ReadView在undo log版本鏈上找到事務可見的數據版本(事務開始時已提交的數據版本),避免臟讀和不可重復讀
    • 只使用MVCC快照讀讀取固定的一個數據版本,不會出現幻讀問題。
  • 鎖機制

    • 當前讀使用臨鍵鎖,防止幻讀和臟寫
    • 只使用當前讀,或第一次讀操作是當前讀,會對查詢的數據范圍加臨鍵鎖,即便之后在鎖范圍內再使用快照讀也不會出現幻讀問題。但是如果之后的快照讀不在鎖定范圍并且又使用當前讀暴露了其他事務的修改,也會出現不可重復讀和幻讀。
  • 仍存在的幻讀問題

    • 快照讀當前讀混合讀

      • 由快照讀讀取事務開始時的數據版本變成讀取最新版本的當前讀,且中間有其他事務修改該數據。
      -- 事務 A (RR)
      BEGIN;
      -- 快照讀:基于 MVCC 首次 Read View
      SELECT * FROM users WHERE age > 20; -- 返回 2 行 (id=30,40)-- 事務 B 插入并提交:INSERT INTO users(age) VALUES(25); -- id=50-- 當前讀:直接讀取最新數據(繞過 MVCC)
      SELECT * FROM users WHERE age > 20 FOR UPDATE; -- 返回 3 行 (id=30,40,50)
      COMMIT;
      
      • 快照讀更新操作引發數據可見(隱式當前讀與快照讀混用)

        • 更新使其他事務插入的行可見
        -- 事務A (RR)
        BEGIN;
        SELECT * FROM users WHERE age>20; -- 快照讀:返回id=30 (age=30)-- 事務B:INSERT INTO users(age) VALUES(25); COMMIT;UPDATE users SET status=1 WHERE age>20; -- 當前讀:更新id=30和id=新行
        SELECT * FROM users WHERE age>20;       -- 看到id=30和id=新行
        
        • 事務與其他事務更新不同列
        -- 事務A (RR)
        BEGIN;
        SELECT * FROM users WHERE id=1; -- 看到(name='A', age=20)-- 事務B:UPDATE users SET name='B' WHERE id=1; COMMIT;-- 事務A更新不同列
        UPDATE users SET age=21 WHERE id=1; -- 當前讀:基于(name='B', age=20)更新
        SELECT * FROM users WHERE id=1;     -- 看到(name='B', age=21)
        
        • 其他事務刪除數據
        -- 事務A (RR)
        BEGIN;
        SELECT * FROM users WHERE id=1; -- 看到數據-- 事務B:DELETE FROM users WHERE id=1; COMMIT;UPDATE users SET age=21 WHERE id=1; -- 0 rows affected(數據已不存在)
        SELECT * FROM users WHERE id=1;     -- 無結果
        
        • 本事務與其他事務修改不同行
        -- 初始數據:id=1, col1=100, col2=200
        -- 事務A (RR)          | 事務B
        ----------------------|-------------------
        BEGIN;                |
        SELECT col1 FROM t;   | BEGIN;
        --> 100               || UPDATE t SET col2=300;| COMMIT;
        UPDATE t SET col1=150;|
        SELECT * FROM t;      |
        --> col1=150, col2=300|
        
    • 原因分析

      • RR 通過事務開始時固定的 ReadView 確保快照讀避免不可重復讀和幻讀。但更新操作(隱式當前讀)會繞過 ReadView 直接讀取最新數據版本,繼承其他事務的修改(包括插入/刪除/更新),并將修改后的數據以本事務 ID 寫入新版本。這導致:
        1. 若其他事務插入新行且匹配更新條件 → 幻讀
        2. 若其他事務更新同一行 → 不可重復讀
        3. 若其他事務刪除行且嘗試更新該行 → 行消失(不可重復讀)
    • 解決辦法

      • 讀操作使用加鎖讀,也是串行化的解決方案嗎,但業務中可考慮上述情況是否會出現。
串行化S
  • MVCC:
    • 禁止快照讀,所有讀裝換為當前讀。
  • 鎖機制
    • 將普通讀操作隱式加**SELECT ... FOR SHARE(共享鎖)**。
    • 每次讀操作都會對查詢范圍內的數據行和間隙加臨鍵鎖,徹底避免幻讀不可重復讀

事務原理

Undo Log回滾

前像版本
  • 事務回滾要將數據恢復到前像版本,而前像版本指的是數據行隱藏字段DB_ROLL_PTR指向的undo log版本鏈的直接前驅版本,從最新的修改開始執行create_trx_id是當前事務id的版本鏈的反向邏輯就能恢復行數據版本
  • DB_ROLL_PTR指向的版本鏈中的版本一定是在該版本創建時已提交的事務修改的,mysql的寫操作是隱式加鎖讀(當前讀),對同一數據行的寫操作事務一定是串行執行的
  • 除了可用于回滾的直接前驅版本,也就是更早版本,依然存在是MVCC給其他未提交且可見此版本的事務用于快照讀的。

不同類型的 Undo Log 中舊版本的存儲內容和回滾操作

操作類型存儲內容回滾操作
UPDATE被修改前行數據的完整版本(含所有字段舊值)用 undo log 中記錄的舊值覆蓋當前行數據,恢復 DB_TRX_IDDB_ROLL_PTR 為修改前的狀態,撤銷字段更新。
DELETE整行數據的完整版本(含所有字段舊值,相當于特殊更新的舊狀態)清除行的刪除標記(DELETE_BIT),用 undo log 中的舊值恢復行數據可見性,DB_TRX_IDDB_ROLL_PTR 回退到刪除前的版本。
INSERT新插入行的完整主鍵信息(主鍵值及元數據)根據主鍵定位到插入的行,執行物理刪除(因插入行未提交,其他事務不可見,刪除后無殘留)。
回滾核心流程:逆向遍歷 undo log 并執行反向操作

回滾過程會從事務的最后一個修改操作開始,逆向遍歷事務的 undo log 鏈表,逐個對每個操作執行 “反向邏輯”,直到所有修改被撤銷。具體步驟如下:

步驟 1:定位事務的 undo log 鏈表

InnoDB 通過事務 ID 找到該事務對應的 undo log 鏈表,鏈表的 “頭節點” 是事務最后一次修改生成的 undo log 記錄,“尾節點” 是事務第一次修改生成的 undo log 記錄。

步驟 2:從最后一個修改開始逆向處理

回滾按 “逆序” 處理每個 undo log 記錄(即先撤銷最后執行的操作,再撤銷倒數第二個,以此類推),確保數據恢復的正確性。以下按操作類型分述:

場景 1:撤銷 INSERT 操作(基于 Insert undo log)

  • undo log 內容:記錄了插入行的完整數據(含主鍵)。
  • 反向操作:根據 undo log 中的主鍵定位到插入的行,直接刪除該行(因為插入的行在事務提交前僅對當前事務可見,刪除后其他事務無法感知)。
  • 示例:事務內執行 INSERT INTO user VALUES (1, '張三'),回滾時通過 Insert undo log 找到 id=1 的行,執行刪除。

場景 2:撤銷 UPDATE 操作(基于 Update undo log)

  • undo log 內容:記錄了被修改行的完整舊版本數據(修改前的所有字段值)。
  • 反向操作:根據 undo log 中的主鍵定位到數據行,用舊版本數據覆蓋當前版本(即恢復 DB_TRX_ID 為舊版本的事務 ID,DB_ROLL_PTR 指向舊版本的前驅 undo log)。
  • 示例:事務內先執行 UPDATE user SET age=30 WHERE id=1(原 age=20),回滾時通過 Update undo log 找到 id=1 的行,將 age 恢復為 20,DB_ROLL_PTR 指向修改前的舊版本 undo log。

場景 3:撤銷 DELETE 操作(基于 Update undo log)

  • undo log 內容:記錄了被刪除行的完整舊版本數據(刪除前的所有字段值)。
  • 反向操作:根據 undo log 中的主鍵定位到被標記刪除的行,恢復其數據為舊版本(清除刪除標記 delete_flag),并更新 DB_TRX_IDDB_ROLL_PTR 為舊版本信息。
  • 示例:事務內執行 DELETE FROM user WHERE id=1,回滾時通過 Update undo log 找到 id=1 的行,恢復其數據(取消刪除標記),使其可見性恢復到刪除前的狀態。
初始狀態
賬戶表 (accounts)
+----+-------+---------+
| id | name  | balance |
+----+-------+---------+
| 1  | Alice | 1000.00 |
| 2  | Bob   |  500.00 |
+----+-------+---------+
事務操作序列
BEGIN;  -- 事務A開始
-- 操作1:Alice轉出100
UPDATE accounts SET balance = 900.00 WHERE id = 1;
-- 操作2:Bob轉入100
UPDATE accounts SET balance = 600.00 WHERE id = 2;
數據頁未持久化

初始狀態

臟頁
臟頁
Buffer Pool
Page1: id=1, balance=900
Page2: id=2, balance=600
Undo Log
記錄1: id=1, old_balance=1000
記錄2: id=2, old_balance=500

回滾流程

回滾線程Undo LogBuffer Pool1. 讀取操作1前像: id=1, balance=10002. 恢復Page1: balance=10003. 讀取操作2前像: id=2, balance=5004. 恢復Page2: balance=500清除臟頁標記回滾線程Undo LogBuffer Pool

結果

  • 內存數據恢復為每項修改數據的前像
  • 磁盤數據保持 (無需操作)
  • 無磁盤 I/O 發生

數據頁全部持久化

初始狀態

操作1持久化:Page1已刷盤 → id=1, balance=900
操作2持久化:Page2已刷盤 → id=2, balance=600Undo Log:記錄1: id=1, old_balance=1000記錄2: id=2, old_balance=500

回滾流程

回滾線程Undo LogBuffer PoolRedo Log后臺線程磁盤1. 讀取操作1前像: balance=10002. 恢復Page1內存: balance=10003. 生成回滾1的Redo4. 讀取操作2前像: balance=5005. 恢復Page2內存: balance=5006. 生成回滾2的Redo刷寫兩個回滾Redo刷Page1 (balance=1000)刷Page2 (balance=500)loop[異步刷盤]回滾線程Undo LogBuffer PoolRedo Log后臺線程磁盤

關鍵步驟詳解

  1. 內存回滾
    • 立即將內存中的數據恢復為前像值
    • 緩沖池標記為臟頁(因為與磁盤不一致)
  2. Redo Log 保護
    • 保證回滾操作本身的持久性
  3. 數據頁刷盤
    • 后臺線程將恢復后的數據刷到磁盤
    • 刷盤過程依然通過 Doublewrite 防止頁斷裂
部分數據頁持久化

初始狀態

操作1持久化:Page1已刷盤 → id=1, balance=900
操作2未持久化:Page2在內存 → id=2, balance=600 (臟頁)Undo Log:記錄1: id=1, old_balance=1000記錄2: id=2, old_balance=500

回滾流程

回滾線程Undo LogBuffer PoolRedo Log后臺線程磁盤1. 讀取操作1前像: id=1, balance=10002. 恢復Page1內存: balance=10003. 生成回滾1的Redo記錄4. 讀取操作2前像: id=2, balance=5005. 恢復Page2內存: balance=500刷寫回滾Redo記錄刷Page1 (balance=1000)loop[異步刷盤]回滾線程Undo LogBuffer PoolRedo Log后臺線程磁盤

關鍵步驟詳解

  1. 內存回滾
    • 不論是否持久化都將內存中的數據恢復為前像值
    • 已經持久化的數據頁將緩沖池標記為臟頁(因為與磁盤不一致)
    • 未持久化的數據頁將臟頁標識去除(與磁盤數據一致,無需刷盤)
  2. Redo Log 保護
    • 保證回滾操作本身的持久性
  3. 數據頁刷盤
    • 后臺線程將恢復后的數據刷到磁盤
    • 刷盤過程依然通過 Doublewrite 防止頁斷裂

Redo Log

  • 重做日志,記錄的是事務提交時數據頁的物理修改,在刷新臟頁到磁盤中發生錯誤時或數據庫崩潰時,用于數據恢復,以實現事務持久性

  • 組成Redo Log Buffer(重做日志緩沖)和Redo Log File(重做日志文件),前者在內存中,后者在磁盤中

  • 臟頁:在執行事務的增刪改操作時會先對內存中的Buffer Pool緩沖池進行修改,如果緩沖池中不存在則會由后臺線程將數據從磁盤中讀出存放在緩沖池中,并對數據進行修改,修改后的數據頁就稱為臟頁(與磁盤中數據不一致)。

  • Redo Log解決的問題:后臺線程會在一定時機將臟頁刷新到磁盤中,但刷新不是實時的,如果事務已提交并返回成功,但是如果在未成功刷盤時出錯或崩潰

    • 導致已提交的事務丟失,事務的持久性就未能保證。
    • 未提交的事務的部分數據頁被刷新到磁盤中,導致數據不一致
WAL日志先行
  • 日志先行,所有的數據頁修改前必須先將對應的修改記錄寫入到日志,并保證日志落盤以保證事務的持久性。

  • 工作流程

    • 事務執行階段

      事務在修改數據頁時會同步生成redo log記錄(包括表空間ID、頁號、偏移量、修改值等物理信息)

      • 物理邏輯日志:記錄頁級別的物理修改,而非 SQL 語句
      • 實時生成:每條數據修改都立即產生日志
      • 內存緩沖:日志暫存內存,未直接落盤
      • 設計目的:利用內存緩沖避免每次修改都觸發磁盤 I/O,大幅提升事務執行效率。
    事務Buffer PoolLog Buffer修改數據頁(產生臟頁)生成Redo記錄(物理邏輯日志)記錄格式:表空間ID, 頁號, 偏移量, 修改值事務Buffer PoolLog Buffer
    • 事務提交階段
      • 事務提交時,根據innodb_flush_log_at_trx_commit參數決定日志的刷盤策略。
        • 策略1(默認安全):立即將 Log Buffer 中的日志刷到磁盤文件
        • 策略2(平衡):僅寫入操作系統緩存
        • 策略0(高性能):依賴后臺線程異步刷盤
      • 保證事務提交時,相關redo log至少進入操作系統持久化層,滿足事務的持久化要求。
    innodb_flush_log_at_trx_commit=1
    =2
    =0
    事務提交
    刷盤策略
    立即刷Redo Log到磁盤
    寫OS緩存
    等待后臺線程刷盤
    返回提交成功
    • 后臺處理階段
      • 當日志文件寫滿 75% 時,觸發 Checkpoint(檢查點)
      • 將內存中最早的臟頁刷入磁盤
      • 更新系統表空間中的 checkpoint_lsn
      • 回收已刷盤日志的存儲空間
    日志寫滿75%
    觸發Checkpoint
    刷臟頁到磁盤
    推進checkpoint_lsn
    回收舊日志空間
    • 崩潰恢復階段
      • 定位系統表空間中的checkpoint_lsn(最近一次刷盤成功的點)
      • 從LSN開始掃描Redo Log文件
      • Redo重做:重新應用Redo Log中的所有日志記錄,恢復數據頁狀態。
        • 注意:Redo重做操作并不是直接去修改磁盤上的數據頁,而是將redolog記錄的修改應用到緩沖池中對應的數據頁上。如果緩沖池中沒有對應的數據頁,則從磁盤讀取到緩沖池,然后在緩沖池中應用Redo Log的修改。
      • Undo回滾:根據Undo Log回滾所有未提交事務的修改(這些事務無法繼續完成,回滾保證一致性)。
        • 未持久化修改:恢復內存數據前像,與磁盤數據一致,去除臟頁標識。
        • 已持久化修改:恢復內存數據前像,與磁盤數據不一致,標記臟頁,添加回滾Redo,刷盤后將數據恢復值前像版本。
    CrashRedoBufferPoolUndoDisk數據庫重啟讀取checkpoint_lsn掃描Redo Log重放日志(LSN1001)重放日志(LSN1002)數據頁變為事務后狀態進入Undo階段讀取Undo Log回滾id=1 (1000.00)回滾id=2 (500.00)刷回原始狀態清理事務狀態CrashRedoBufferPoolUndoDisk
    • 如果不應用redo log,那么想保證事務的持久性,就要在事務提交時,將所有被該事務修改的臟頁同步到磁盤中,這些臟頁可能在磁盤中分散的位置,所以同步操作會涉及到大量的隨機磁盤IO
    • WAL日志先行的機制下,讀數據頁的修改會以日志形式記錄在redo log buffer,在事務提交時再將日志持久化到redo log文件中,而寫入redolog文件的操作是追加寫,只是一種高效的順序寫IO
    • 在redolog持久化到磁盤后,事務的持久性就已經被保證,即使數據庫崩潰也可以依靠redo重放來恢復修改,所以緩沖池中臟頁的刷盤就可以是
      • 延遲的
        • 降低提交延遲,用戶能更快得到提交成功的響應,
        • 增加合并機會,讓后續可能對一個頁的修改在緩沖池中合并,最終只刷一次盤。
      • 批量的
        • 分攤磁盤IO開銷,一次磁盤IO的時間成本被分攤到了多個數據頁上,平均每個頁的IO成本降低。
        • 分攤系統調用開銷,一次系統調用的成本被多個數據頁分攤。
      • 可優化的
        • 操作系統IO調度器,會嘗試對批量的請求進行排序(如類似電梯算法 - SCAN或C-SCAN),使磁頭移動路徑更短,減少隨機磁盤IO的性能損耗。

    WAL將隨機數據修改轉化為順序日志寫入,避免每次修改都觸發磁盤 I/O,大幅提升事務執行效率,并且延遲刷盤可以增加臟頁修改合并機會。

事務原理實現

原子性 (Atomicity): “要么全做,要么全不做”

  • 核心機制: Undo Log
  • 實現過程:
    • 執行任何修改(INSERT/UPDATE/DELETE,先在 Undo Log 中記錄修改前的數據狀態(舊值或反向操作邏輯)。(注意:寫入 Undo Log 本身也是一個修改,會被 Redo Log 記錄以保證 Undo Log 的持久性)
    • 修改內存中的數據頁(產生臟頁)。
    • 提交 (Commit):
      • 生成包含 COMMIT 標記的 Redo Log 記錄并 強制刷盤 (fsync)(此時持久性已保證)
      • 臟頁異步刷盤。
    • 回滾 (Rollback) / 失敗:
      • 引擎根據 Undo Log 中的記錄,執行邏輯逆操作(如 DELETE 的逆操作是 INSERTUPDATE 是恢復舊值),將數據恢復到事務開始前的狀態。
    • 關鍵點: Undo Log 提供了將事務所有修改“撤銷”回去的能力。無論提交還是回滾,事務內的操作被視為一個不可分割的整體。Redo Log 保證了 Undo Log 操作本身的可靠性。

一致性 (Consistency): “數據庫總是從一個一致狀態轉換到另一個一致狀態”

  • 核心機制: ACID 共同目標 + 數據庫約束 + 應用邏輯
  • 實現過程:
    • 原子性 確保事務邊界內的轉換是原子的,不會停留在中間不一致狀態。
    • 隔離性 防止并發事務看到彼此未完成的不一致修改。
    • 持久性 確保提交的狀態是永久的,不會因崩潰丟失導致狀態回退。
    • 數據庫約束 (主鍵、外鍵、唯一、非空、CHECK):在事務執行過程中(通常在語句級或事務提交時)進行校驗。違反約束的操作會被拒絕,觸發回滾(依賴 Undo Log)。
    • 應用邏輯:業務規則需要開發者在事務代碼中正確實現。
    • 關鍵點: A、I、D 是實現 C 的基礎手段。Undo Log 在回滾違反約束的操作、MVCC 在提供一致性讀視圖上都對一致性有直接貢獻。

隔離性 (Isolation): “并發執行的事務相互隔離,感覺像串行執行”

  • 核心機制: 鎖機制 + MVCC (基于 Undo Log)
  • 實現過程:
    • 寫-寫沖突 (核心:鎖機制):
      • 當一個事務要修改某數據項時,必須先獲得相應的鎖(如行鎖、X鎖)。
      • 其他事務試圖修改同一數據項時會被阻塞(或根據隔離級別報錯),直到鎖釋放。這保證了同一時間只有一個事務能修改特定數據,防止數據被并發寫破壞。
      • 例如(Repeatable Read):事務A修改行R時加X鎖,事務B嘗試修改R會被阻塞直到A提交/回滾釋放鎖。
    • 讀-寫沖突 (核心:MVCC + Undo Log):
      • MVCC 基礎: 每行數據包含隱藏字段 DB_TRX_ID(最后修改它的事務ID)和 DB_ROLL_PTR(指向該行在 Undo Log 中舊版本記錄的指針),形成數據行的版本鏈
      • 快照讀 (非鎖定讀): 當讀操作發生時(在 RC 或 RR 級別下):
        • 系統根據事務啟動時刻(或語句開始時刻,取決于隔離級別)生成一個 Read View。Read View 包含當時所有活躍(未提交)事務ID列表。
        • 通過 DB_ROLL_PTR 遍歷版本鏈。
        • 找到滿足以下條件的版本:
          • 創建該版本的事務ID < Read View 中最小活躍事務ID (說明該版本在事務開始時已提交)
          • 創建該版本的事務ID 在 Read View 中但等于自身事務ID (說明是自己修改的)
          • 該版本的 DB_TRX_ID 是鏈中滿足上述條件的最大值 (即該事務開始時能看到的最新已提交版本)
        • 讀取該版本的數據(存儲在 Undo Log 中)。讀操作不阻塞寫操作,寫操作也不阻塞讀操作。
      • 例如(Repeatable Read):事務A開始時生成Read View V1。事務B在A之后修改并提交了行R。事務A再次讀R時,通過V1和Undo Log鏈,仍然會讀到B修改前的版本(快照)。
    • 關鍵點: 鎖機制 直接處理并發寫,強制串行化寫操作。MVCC 利用 Undo Log 提供的歷史版本,為讀操作提供一致性視圖,解決了讀寫沖突,極大提高了并發讀性能。不同的隔離級別(RC, RR)主要通過調整 Read View 的生成時機(語句級/事務級)和鎖的范圍(如 RR 的間隙鎖)來實現。Redo Log 保證了 Undo Log 版本鏈的持久性,支撐 MVCC 在崩潰恢復后仍有效。

持久性 (Durability): “一旦事務提交,修改永久保存”

  • 核心機制: Redo Log + WAL 原則
  • 實現過程:
    • 事務提交時,其產生的所有修改操作(包括數據修改和 Undo Log 的寫入)對應的 Redo Log 記錄(物理日志),以及一個標識事務提交的 COMMIT 記錄,必須被 強制刷盤 (fsync) 到持久化存儲(Redo Log File)中。這是 WAL 原則的核心要求。
    • 此時,即使系統立即崩潰,這些修改操作已安全保存在磁盤上。
    • 內存中被修改的數據頁(臟頁)不需要在提交時立即刷盤。數據庫會在后臺選擇合適的時間(Checkpoint 機制),將臟頁批量、異步地寫回磁盤數據文件。這極大提高了性能(將隨機寫轉化為順序寫 + 延遲批量刷臟頁)。
    • 崩潰恢復:
      • 數據庫重啟時,首先定位到 Redo Log 中最近的 Checkpoint(記錄了當時哪些臟頁已刷盤)。
      • 從 Checkpoint 開始掃描 Redo Log。
      • 重做 (Redo): 重新執行所有 Checkpoint 之后、日志末尾之前的、且帶有 COMMIT 標記的 Redo Log 記錄對應的操作。這確保了所有已提交事務的修改都被重新應用到數據文件。
      • 回滾 (Undo): 對于 Redo Log 中存在但沒有 COMMIT 標記的事務(崩潰時未提交的事務),利用 Undo Log 進行回滾(原理同原子性中的回滾),撤銷這些未完成事務的修改。
    • 關鍵點: 強制刷盤 Redo Log (含Commit標記) 是持久性的絕對保證。異步刷臟頁是性能優化。崩潰恢復中的 Redo 階段確保了已提交修改不丟失,Undo 階段(依賴 Undo Log)保證了未提交修改被清除,共同維護了數據庫狀態的一致性。Undo Log 本身的寫入也受 Redo Log 保護。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/93700.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/93700.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/93700.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Linux系統之部署nullboard任務管理工具

Linux系統之部署nullboard任務管理工具一、nullboard介紹1.1 nullboard簡介1.2 任務看板工具介紹1.3 nullboard使用場景二、本次實踐介紹2.1 本地環境規劃2.2 本次實踐介紹三、安裝httpd軟件3.1 檢查yum倉庫3.2 安裝httpd軟件3.3 啟動httpd服務3.4 查看httpd服務狀態3.5 防火墻…

Qt設置軟件使用期限【新版防修改系統時間】

在工業軟件或其他領域中&#xff0c;經常會對軟件進行授權&#xff0c;軟件需要付費進行有期限的使用。以下是我用Qt設計的設置軟件使用期限的兩種方案。 主體思想&#xff1a; 1.軟件需要綁定機器&#xff0c;讓用戶無法通過復制在另一臺機器上運行。 2.由廠家提供激活碼供用戶…

【JavaEE】多線程(線程安全問題)

有些代碼在單個線程環境下執行正確&#xff0c;如果同樣的代碼在多個線程下同時執行可能就會出現問題&#xff0c;這個就是線程安全問題&#xff08;或者稱線程不安全問題&#xff09;&#xff0c;簡而言之就是&#xff1a;線程安全問題是由于多線程出現的問題&#xff0c;原因…

NodeJs 桌面開發學習 electron.js (一)

今天開始學習NodeJs 關于 桌面應用的內容&#xff0c;長期目標是 React electron 實現一個桌面應用。今天先實現一個簡單的目標&#xff0c;搭建一個Electron ts 項目架構&#xff0c;并實現主業務線程 和前端渲染線程的交互一、代碼結構和配置例子項目結構大致如下&#xff…

diffusion model(1.4) 相關論文閱讀清單

以下是閱讀清單&#xff1a; 《Deep Unsupervised Learning using Nonequilibrium Thermodynamics》擴散模型&#xff0c;arxiv鏈接《Denoising Diffusion Probabilistic Models》DDPM論文 arxiv鏈接

ESP32-C3_SMARTCAR

前言: 前面用stm32f103c8t6 rt-thread 寫了個智能小車程序 這章用esp32-c3 重新來遍 1&#xff1a;環境 vscodeidf5.4 esp32-3c 找到一塊MIN的底板 湊合用&#xff08;138 cm左右&#xff09; 一個L298N 一個船型開關&#xff0c; 一個665mm 2腳按鈕 鋰電池 186502 及電池盒&a…

消費者API

目錄獨立消費者案例&#xff08;訂閱主題&#xff09;獨立消費者案例&#xff08;訂閱分區&#xff09;消費者組案例獨立消費者案例&#xff08;訂閱主題&#xff09; package com.tsg.kafka.consumer;import org.apache.kafka.clients.consumer.ConsumerConfig; import org.ap…

C# NX二次開發:操作按鈕控件Button和標簽控件Label詳解

大家好&#xff0c;今天介紹ug二次開發過程中的一個叫操作按鈕的控件&#xff0c;這個控件在塊UI編輯器中可以使用。 ? Button這個控件的屬性和方法如下所示&#xff1a; namespace NXOpen.BlockStyler { public class Label : UIBlock { protected intern…

Vue.prototype 的作用

在 Vue.js 中&#xff0c;Vue.prototype 是用來向所有 Vue 實例添加屬性或方法的機制。通過它添加的屬性或方法可以在所有 Vue 組件實例中通過 this 訪問。主要作用添加全局方法或屬性&#xff1a;可以在所有組件中使用的工具方法或常量擴展 Vue 功能&#xff1a;添加 Vue 本身…

Javaee 多線程 --進程和線程之間的區別和聯系

文章目錄進程和線程進程線程進程和線程的區別創建線程的五種寫法繼承Thread,重寫run實現Runnable(接口)&#xff0c;重寫run繼承Thread,重寫run,但是使用匿名內部類實現Runnable(接口)&#xff0c;重寫run&#xff0c;但是使用匿名內部類使用lambda表達式請說明Thread類中run和…

企業如何讓內部視頻僅限指定域名播放,確保視頻不被泄露?

在數字化辦公時代&#xff0c;企業內部的培訓視頻、產品演示或機密會議錄像等敏感內容&#xff0c;一旦被非法傳播或泄露&#xff0c;可能帶來嚴重的商業風險。如何確保這些視頻只能在公司官網或指定域名播放&#xff0c;防止被惡意下載、盜鏈或二次傳播&#xff1f;今天介紹一…

端口映射原理操作詳解教程:實現外網訪問內網服務,本地路由器端口映射公網ip和軟件端口映射域名2種方法

端口映射作為一種不同網絡間通信的關鍵網絡技術&#xff0c;在遠程訪問和內外網連接服務需求日益增長的如今&#xff0c;理解端口映射的原理和設置方法是確保網絡服務可用性的必要技能。本文將深入探討端口映射的基本概念、路由器端口映射設置步驟以及無公網IP用端口映射軟件映…

【PyTorch】多對象分割項目

對象分割任務的目標是找到圖像中目標對象的邊界。實際應用例如自動駕駛汽車和醫學成像分析。這里將使用PyTorch開發一個深度學習模型來完成多對象分割任務。多對象分割的主要目標是自動勾勒出圖像中多個目標對象的邊界。 對象的邊界通常由與圖像大小相同的分割掩碼定義&#xf…

SSH 使用密鑰登錄服務器

用這種方法遠程登陸服務器的時候無需手動輸入密碼 具體步驟 客戶端通過 ssh-keygen 生成公鑰和私鑰 ssh-keygen -t rsa 生成的時候會有一系列問題&#xff0c;根據自己的需要選擇就行。生成的結果為兩個文件&#xff1a; 上傳公鑰至服務器&#xff0c;上述兩個文件一般在客戶…

MySQL 8.4 企業版啟用TDE功能和表加密

一、系統環境操作系統&#xff1a;Ubuntu 24.04 數據庫:8.4.4-commercial for Linux on x86_64 (MySQL Enterprise Server - Commercial)二、安裝TDE組件前提&#xff1a;檢查組件文件是否存在ls /usr/lib/mysql/plugin/component_keyring_encrypted_file.so1.配置全局清單文件…

【Altium designer】導出的原理圖PDF亂碼異常的解決方法

一、有些電源名字無法顯示或器件丟失 解決辦法 (1)首先AD18以及以上的新版本AD不存在該問題。 (2)其次AD17以及更舊版本的AD很可能遇到該問題,參考如下博客筆記進行操作即可: 大致的操作如下:DXP → Preferences → Schematic → Options里面“Render Text with GDI+”…

4.Ansible自動化之-部署文件到主機

4 - 部署文件到受管主機 實驗環境 先通過以下命令搭建基礎環境&#xff08;創建工作目錄、配置 Ansible 環境和主機清單&#xff09;&#xff1a; # 在控制節點&#xff08;controller&#xff09;上創建web目錄并進入&#xff0c;作為工作目錄 [bqcontroller ~]$ mkdir web &a…

Vuex的使用

Vuex 超詳細使用教程&#xff08;從入門到精通&#xff09;一、Vuex 是什么&#xff1f;Vuex 是專門為 Vue.js 設計的狀態管理庫&#xff0c;它采用集中式存儲管理應用的所有組件的狀態。簡單來說&#xff0c;Vuex 就是一個"全局變量倉庫"&#xff0c;所有組件都可以…

pytorch 數據預處理,加載,訓練,可視化流程

流程定義自定義數據集類定義訓練和驗證的數據增強定義模型、損失函數和優化器訓練循環&#xff0c;包括驗證訓練可視化整個流程模型評估高級功能擴展混合精度訓練?分布式訓練?{:width“50%” height“50%”} 定義自定義數據集類 # #1. 自定義數據集類 # class CustomImageD…

Prompt工程:OCR+LLM文檔處理的精準制導系統

在PDF OCR與大模型結合的實際應用中&#xff0c;很多團隊會發現一個現象&#xff1a;同樣的OCR文本&#xff0c;不同的Prompt設計會產生截然不同的提取效果。有時候準確率能達到95%&#xff0c;有時候卻只有60%。這背后的關鍵就在于Prompt工程的精細化程度。 &#x1f3af; 為什…