博客 / 詳情

返回

MySQL 核心模塊揭秘 | 01 期 | 事務的起源:事務池和管理器的初始化

愛可生開源社區 2024 全新技術專欄《MySQL 核心模塊揭秘》第一期。

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

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

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

1. 事務池和管理器

作為 MySQL 中支持事務的默認存儲引擎,InnoDB 對錶中數據的讀寫操作都在事務中執行。

MySQL 被設計為支持高併發,支持很多客户端同時連接到數據庫,這些連接可以同時執行 SQL。

如果這些 SQL 都要讀寫 InnoDB 表,InnoDB 會為每個連接啓動一個事務,這意味着需要同時啓動很多事務。

對於 TP 場景,通常情況下,事務都會很快執行完成。啓動事務、執行 SQL、提交事務的整個流程只會持續很短的時間。

TP 是 OLTP 的簡稱,表示在線事務處理;與之相對的另一個常用術語 AP,是 OLAP 的簡稱,表示在線事務分析。

以這樣一個場景為例:

  • 客户端連接到 MySQL。
  • 客户端執行 begin 語句。
  • 客户端執行一條 update 語句,按主鍵 ID 更新一條記錄。

    這個步驟中,InnoDB 會在執行 update 語句之前,真正啓動一個事務。
  • 客户端執行 commit 語句。
  • 客户端關閉數據庫連接。InnoDB 會在這一步釋放事務。

在這個場景下,InnoDB 事務從啓動到釋放的整個生命週期,有可能只持續 1 ~ 2 毫秒(甚至更短)。

由於要存放事務 ID、事務狀態、Undo 日誌編號、事務所屬的用户線程等信息,每個事務都有一個與之對應的對象,我們稱之為事務對象

每個事務對象都要佔用內存,如果每啓動一個事務都要為事務對象分配內存,釋放事務時又要釋放內存,會降低數據庫性能。

為了避免頻繁分配、釋放內存對數據庫性能產生影響,InnoDB 引入了事務池(Pool),用於管理事務。

顧名思義,事務池是一個池子,這個池子存放的東西既不是水,也不是酒,而是事務對象。

對比我們生活中的各種池子,例如:水池、洗手池、池塘,都是有大小限制的。

事務池也一樣有大小限制,不能無限制的存放事務對象。數據庫繁忙的時候,有很多很多事務對象,需要多個事務池來管理。

事務池多了之後,又會引發另一些問題,例如:

  • 怎麼創建新的事務池?
  • 客户端創建了一個新的數據庫連接,要獲取一個新的事務對象,從哪個事務池獲取?
  • 其它問題...

為了解決這些問題,InnoDB 又引入了事務池管理器(PoolManager),用於管理事務池。

MySQL 啓動過程中,InnoDB 先創建事務池管理器,然後,事務池管理器創建並初始事務池。

2. 創建事務池管理器

InnoDB 整個生命週期中,事務池管理器只有一個,它有個很重要的屬性(m_size),用於指定每個事務池能用多大內存來存放事務對象。

這個屬性值來源於一個硬編碼的常量值,代碼裏是這樣定義的:

/** Size of on trx_t pool in bytes. */
static const ulint MAX_TRX_BLOCK_SIZE = 1024 * 1024 * 4;

這意味着每個事務池能用來存放事務對象的內存是 4194304 字節,也就是 4M。

MySQL 啓動過程中,事務池管理器只會創建並初始化一個事務池。

這個事務池會放入事務池管理器的 m_pools 屬性。這個屬性是個數組(vector),用於管理所有事務池。

創建事務池的過程中,InnoDB 會分配一塊 4M 的內存用於存放事務對象。

每個事務對象的大小為 992 字節,4M 內存能夠存放 4194304 / 992 = 4228 個事務對象。

3. 初始化事務池

事務池創建完成之後,就該初始化了。事務池的初始化,主要是為了得到一些事務對象。

事務池有一個隊列,用於存放已經初始化的事務對象。我們稱這個隊列為事務隊列

一個事務池有 4M 內存可以存放事務對象,這塊內存會被分隔成 4228 個小塊。每初始化一塊小內存,就會得到一個事務對象,這個事務對象會被放入事務隊列。

InnoDB 初始化事務池的過程中,不會初始化全部的 4228 塊小內存,只會初始化最前面的 16 塊小內存,得到 16 個事務對象並放入事務隊列。

初始事務池完成之後,事務隊列中只有 16 個事務對象。

那麼,剩餘的 4212 塊小內存什麼時候會被初始化?

它們會在這種情況下被初始化:啓動過程中初始化的 16 個事務對象都被取走使用了,事務隊列變成空隊列了。

此時,需要再分配一個事務對象用於啓動新事務,InnoDB 就會把剩餘的 4212 塊小內存全部初始化,得到 4212 個事務對象並放入事務隊列。

有一點需要説明,不管是啓動過程中初始化的 16 塊小內存,還是運行過程中初始化的 4212 塊小內存,都是在循環裏一個一個初始化的。每一輪循環都要幹兩件事:

  • 初始化一塊小內存,得到一個事務對象。
  • 把事務對象放入事務池的事務隊列中。

初始化小塊內存的過程中,會初始化事務對象的各個屬性。這裏我們就不一一介紹這些屬性了,等到該它們出場的時候,再按需介紹。

4. 總結

InnoDB 只有一個事務池管理器,用於管理 N 個事務池(N >= 1),每個事務池可以管理 4228 個事務對象。

MySQL 啓動過程中,InnoDB 會先創建事務管理器。事務管理器會創建一個事務池,初始化 16 個事務對象放入事務池的事務隊列。

MySQL 運行過程中,如果這 16 個事務對象都正在被使用,InnoDB 需要一個新的事務對象時,會一次性初始化剩餘的 4212 個事務對象並放入事務池的事務隊列。

本期問題:運行過程中,創建一個新的事務池,會分配多少內存?初始化多少個事務對象?
關於本期主題,如果大家有任何疑問,歡迎留言交流!

下期預告:BEGIN 語句會馬上啓動事務嗎?

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

發佈 評論

Some HTML is okay.