博客 / 詳情

返回

MySQL 核心模塊揭秘 | 02 期 | BEGIN 語句會馬上啓動事務嗎?

聊聊最常用也是最簡單的 BEGIN 語句,開始一個事務的過程中都幹了什麼。

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

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

BEGIN 語句會馬上啓動事務嗎?

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

目錄
[TOC]

正文

1. BEGIN 語句的七十二變

我們查看官方文檔中開始一個事務的語法,會發現還挺複雜:

START TRANSACTION
    [transaction_characteristic [, transaction_characteristic] ...]

transaction_characteristic: {
    WITH CONSISTENT SNAPSHOT
  | READ WRITE
  | READ ONLY
}

BEGIN [WORK]

上面眼花繚亂的語法,按照各種組合展開之後,可以得到這些 SQL 語句:

/*  1 */ BEGIN
/*  2 */ BEGIN WORK
/*  3 */ START TRANSACTION
/*  4 */ START TRANSACTION READ WRITE
/*  5 */ START TRANSACTION READ ONLY
/*  6 */ START TRANSACTION WITH CONSISTENT SNAPSHOT
/*  7 */ START TRANSACTION WITH CONSISTENT SNAPSHOT, READ WRITE
/*  8 */ START TRANSACTION WITH CONSISTENT SNAPSHOT, READ ONLY
/*  9 */ START TRANSACTION WITH CONSISTENT SNAPSHOT, READ WRITE, READ ONLY
/* 10 */ START TRANSACTION READ WRITE, READ ONLY

其中,語句 1 ~ 8 都能正常執行,語句 9、10 會報語法錯誤:

(1064, 
 "You have an error in your SQL syntax;
 check the manual that corresponds
 to your MySQL server version 
 for the right syntax to use 
 near '' at line 1")

語句 9、10 報語法錯誤,並不是因為 MySQL 不能識別這兩種語法,而是識別語法之後進行判斷給出的錯誤提示:

start:
  START_SYM TRANSACTION_SYM opt_start_transaction_option_list
  {
    LEX *lex= Lex;
    lex->sql_command= SQLCOM_BEGIN;
    /* READ ONLY and READ WRITE are mutually exclusive. */
    if (($3 & MYSQL_START_TRANS_OPT_READ_WRITE) &&
        ($3 & MYSQL_START_TRANS_OPT_READ_ONLY))
    {
      YYTHD->syntax_error();
      MYSQL_YYABORT;
    }
    lex->start_transaction_opt= $3;
  }
  ;

上面是解析 START TRANSACTION 的部分邏輯,通過以上邏輯可以看到,當 START TRANSACTION 同時包含以下兩項時:

  • MYSQL_START_TRANS_OPT_READ_WRITE
  • MYSQL_START_TRANS_OPT_READ_ONLY

MySQL 會通過 YYTHD->syntax_error() 主動拋出一個語法錯誤,告訴我們不支持這樣的語法。

在可以正常執行的語句 1 ~ 8 中:

  • 語句 1 ~ 4:用於開始一個新的讀寫事務。

    語句 5:用於開始一個新的只讀事務。

    這兩類語句都不需立即創建一致性讀視圖,事務的啓動將延遲至實際需要時。
  • 語句 6 ~ 7:用於開始一個新的讀寫事務。

    語句 8:用於開始一個新的只讀事務。

    這兩類語句都會先啓動事務,隨後立即創建一致性讀視圖。

如果要投票選出我們最常用於開始一個事務的語句,大概非 BEGIN 莫屬了。

接下來,我們就用 BEGIN 作為語句 1 ~ 5 的代表,來聊聊開始一個新事務的過程中,MySQL 做的那些事。

2. BEGIN 語句都幹什麼了?

如果用一個詞語描述 BEGIN 語句要做的事,那就是辭舊迎新,展開來説,BEGIN 語句主要做兩件事:

  • 辭舊:提交老事務。
  • 迎新:準備新事務。

2.1 提交老事務

我們先來看一個場景:

在 MySQL 客户端命令行(mysql)中,我們通過 BEGIN 語句開始了一個事務(事務 1),並且已經執行了一條 INSERT 語句。

事務 1 還沒有提交(即處於活躍狀態),我們在同一個連接中又執行了 BEGIN 語句,事務 1 會發生什麼?

答案是:事務 1 會被提交。

原因是: MySQL 不支持嵌套事務。事務 1 沒有提交的情況下,又要開始一個新事務,事務 1 將無處安放,只能被動提交了。

回到本小節主題,我們來看看 BEGIN 語句提交老事務的流程。

首先,BEGIN 語句會判斷當前連接中是否有可能存在未提交事務,判斷邏輯為:當前連接的線程是否被打上了 OPTION_NOT_AUTOCOMMITOPTION_BEGIN 標誌位(如下代碼所示)。

if (thd->in_multi_stmt_transaction_mode() || ...) {
  ...
}

inline bool in_multi_stmt_transaction_mode() const {
  return variables.option_bits & 
    (OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN);
}

只要 variables.option_bits 包含其中一個標誌位,就説明當前連接中可能存在未提交事務。

BEGIN 語句想要開始一個新事務,就必須先執行一次提交操作,把可能未提交的事務給提交了(如下代碼所示)。

if (thd->in_multi_stmt_transaction_mode() || ...) {
  ...
  res = ha_commit_trans(thd, true);
}

如果 variables.option_bits 沒有包含兩個標誌位中的任何一個,説明當前連接中沒有未提交事務,可以直接開始一個新事務。

2.2 準備新事務

辭舊完事,就該迎新了。

由於 MySQL 一向秉持不鋪張浪費的原則,對於資源,能少分配就少分配、能晚分配就晚分配。

啓動事務也需要分配資源,遵循不鋪張浪費的原則,BEGIN 語句執行過程中,並不會馬上啓動一個新事務,只會為新事務做一點點準備工作。

這個一點點真的是一點點,你看:

thd->variables.option_bits |= OPTION_BEGIN;

上面的準備工作就是給當前連接的線程打上 OPTION_BEGIN 標誌。

有了 OPTION_BEGIN 標誌,MySQL 就不會每次執行完一條 SQL 語句就提交事務,而是需要用户發起 commit 語句才提交事務,這樣的事務就可以執行多條 SQL 了。

3. 總結

一句話總結:BEGIN 語句執行過程中,要做的事情就是辭舊(提交老事務)迎新(準備新事務),並不會馬上啓動一個新事務。

本期問題:對於 START TRANSACTION 同時指定 READ WRITE、READ ONLY,除了報錯,你還有別的思路解決這個問題嗎?歡迎大家留言交流。

下期預告:我是一個事務,請給我一個對象。

user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.