MySQL 核心模塊揭祕 | 14 期 | 回滾整個事務

回滾整個事務要怎麼清除 binlog 日誌,InnoDB 又會進行哪些操作?

作者:操盛春,愛可生技術專家,公衆號『一樹一溪』作者,專注於研究 MySQL 和 OceanBase 源碼。

愛可生開源社區出品,原創內容未經授權不得隨意使用,轉載請聯繫小編並註明來源。

本文基於 MySQL 8.0.32 源碼,存儲引擎爲 InnoDB。

目錄 [TOC]

正文

1. 準備工作

創建測試表:

CREATE TABLE `t1` (
  `id` int unsigned NOT NULL AUTO_INCREMENT,
  `i1` int DEFAULT '0',
  PRIMARY KEY (`id`) USING BTREE,
  KEY `idx_i1` (`i1`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;

插入測試數據:

INSERT INTO `t1` (`id`, `i1`) VALUES
(10, 101), (20, 201),
(30, 301), (40, 401);

示例 SQL:

/* 1 */ begin;
/* 2 */ insert into t1(id, i1)
        values(50, 501);
/* 3 */ insert into t1(id, i1)
        values(60, 601);
/* 4 */ rollback;

每條 SQL 前面的數字是它的編號,4 條 SQL 分別爲 SQL 1、SQL 2、SQL 3、SQL 4,其中,SQL 4 是本文的主角。

SQL 2 插入記錄 <id = 50, i1 = 501> 產生的 undo 日誌編號爲 0。

SQL 3 插入記錄 <id = 60, i1 = 601> 產生的 undo 日誌編號爲 1。

2. binlog 回滾

示例 SQL 的兩條 insert 語句執行過程中,會產生 binlog 日誌,存放到 trx cache 中。

回滾整個事務時,事務執行過程中改變(插入、更新、刪除)的數據都不要了,產生的 binlog 日誌也就沒有用了。

回滾整個事務,首先要進行的步驟就是 binlog 回滾。從這個步驟的名字來看,我們預期 MySQL 會在這一步把 trx cache 中的 binlog 日誌都清除。

不過,我們要失望了,因爲這一步什麼都沒幹。

下面是 binlog 回滾的代碼:

static int binlog_rollback(handlerton *, THD *thd, bool all) {
  DBUG_TRACE;
  int error = 0;
  if (thd->lex->sql_command == SQLCOM_ROLLBACK_TO_SAVEPOINT)
    error = mysql_bin_log.rollback(thd, all);
  return error;
}

從代碼可以看到,只有 thd->lex->sql_command 爲 SQLCOM_ROLLBACK_TO_SAVEPOINT 纔會調用 mysql_bin_log.rollback(thd, all) 執行 binlog 回滾操作。

然而,執行 rollback 語句時,thd->lex->sql_command 爲 SQLCOM_ROLLBACK,不滿足 if 條件,上面的代碼就什麼都不會幹了。

那麼,trx cache 中的 binlog 日誌什麼時候會清除?

別急,後面會有專門的小節介紹。

3. InnoDB 回滾

binlog 回滾操作結束之後,接下來就是 InnoDB 回滾了。

InnoDB 回滾操作,會讀取並解析事務產生的所有 undo 日誌,並執行產生這些 undo 日誌的操作的反向操作,也就是回滾

回滾過程中,會根據 undo 日誌產生的時間,從後往前讀取並解析日誌,再執行這條日誌對應的回滾操作。

示例 SQL 中,執行了兩條 insert 語句,會產生兩條 undo 日誌,編號分別爲 0、1。以主鍵索引爲例,回滾過程如下:

  • 讀取最新的 undo 日誌(編號爲 1)。
  • 解析 undo 日誌得到 <id = 60>
  • 刪除 t1 表中 id = 60 的記錄。
  • 讀取上一條 undo 日誌(編號爲 0)。
  • 解析 undo 日誌得到 <id = 50>
  • 刪除 t1 表中 id = 50 的記錄。
  • 讀取上一條 undo 日誌,沒有了,InnoDB 回滾操作結束。

4. 提交事務

InnoDB 回滾操作完成之後,接下來要怎麼辦?

這其實取決於回滾操作是怎麼進行的。

我最初理解的回滾操作,是把事務執行過程中改變(插入、更新、刪除)的記錄恢復原樣,就像事務什麼都沒幹過一樣。

然而,實際情況沒有這麼理想。

事務執行過程中改變過的那些記錄,回滾之後:

  • 從邏輯上來看,恢復了原樣,確實就像事務什麼都沒幹過一樣。
  • 從物理上來看,可能已經發生了變化,因爲記錄的位置有可能和修改之前不一樣。

嘮叨這麼多,就是想說清楚一件事:事務的回滾操作,不是原地撤銷對數據頁的修改,而是通過再次修改數據頁實現的。

既然修改了數據頁,那就需要執行提交操作,才能讓這些修改生效。

接下來,要執行的操作,就是把 InnoDB 回滾操作過程中對數據頁的修改提交了,也就是提交事務。

不過,這裏的提交事務和 commit 語句提交事務不一樣。

執行 commit 語句時,因爲有 binlog 和 InnoDB 兩個存儲引擎,需要使用二階段提交。

事務執行過程中改變(插入、更新、刪除)記錄,會產生 binlog 日誌。

回滾時,要把記錄再修改回原來的樣子。從邏輯上來看,記錄就像是從來沒有發生過變化,binlog 日誌也就不需要了。

所以,InnoDB 回滾完成之後提交事務,不需要把 trx cache 中的 binlog 日誌寫入 binlog 日誌文件並刷盤,只需要提交 InnoDB 事務就可以了。

關於提交 InnoDB 事務的具體邏輯,可以參照第 11 期《InnoDB 提交事務,提交了什麼?》。

5. 清除 binlog 日誌

trx cache 中的 binlog 日誌有可能一部分存放在內存 buffer 中,另一部分存放在磁盤臨時文件中。

清除操作需要同時清除 trx cache 內存 buffer 和磁盤臨時文件中的 binlog 日誌,分爲兩個步驟進行:

  • 清空內存 buffer,讓 trx cache 的 write_pos 指向內存 buffer 的開始處即可。
  • 清空磁盤臨時文件,首先會把文件的 seek offset 設置爲 0,讓文件本身的位置指針指向文件開頭處,然後截斷磁盤臨時文件,釋放文件佔用的空間。

前面的 binlog 回滾步驟,沒有清除事務執行過程中產生的 binlog 日誌,而是留到 InnoDB 回滾步驟中提交事務完成之後才執行。這是因爲:

  • 清空磁盤臨時文件中 binlog 日誌的過程不可逆,如果中間出現問題,不能回退。
  • InnoDB 回滾步驟中提交事務的容錯性更好,回滾失敗之後就不清除 binlog 日誌了,也不損失什麼。

6. 總結

回滾整個事務,主要分爲三大步驟。

第 1 步,執行 binlog 回滾操作,其實什麼也沒幹。

第 2 步,執行 InnoDB 回滾操作,會把事務執行過程中改變(插入、更新、刪除)的記錄恢復原樣(至少從邏輯上來看是這樣的)。

最後,還會提交 InnoDB 事務,讓回滾操作對數據頁的修改生效。

第 3 步,清除事務執行過程中產生的、臨時存放於 trx cache 中的 binlog 日誌。

本期問題:關於本期內容,如有問題,歡迎留言交流。

更多技術文章,請訪問:https://opensource.actionsky.com/

關於 SQLE

SQLE 是一款全方位的 SQL 質量管理平臺,覆蓋開發至生產環境的 SQL 審覈和管理。支持主流的開源、商業、國產數據庫,爲開發和運維提供流程自動化能力,提升上線效率,提高數據質量。

SQLE 獲取

類型 地址
版本庫 https://github.com/actiontech/sqle
文檔 https://actiontech.github.io/sqle-docs/
發佈信息 https://github.com/actiontech/sqle/releases
數據審覈插件開發文檔 https://actiontech.github.io/sqle-docs/docs/dev-manual/plugins/howtouse
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章