天天看點

InnoDB存儲引擎事務ACID的實作

InnoDB存儲引擎對事務有着良好的支援,完全符合ACID的特性,支援以下幾種事務類型:

  • 扁平事務
  • 帶有儲存點的事務
  • 鍊事務
  • 分布式事務

InnoDB不支援嵌套事務,使用者可通過帶有儲存點的事務來模拟串行的嵌套事務。

InnoDB對事務ACID的支援由多種機制實作:

  • 事務隔離性由鎖來實作,包含表鎖、行鎖
  • 原子性、持久性由InnoDB的

    redo log

    (重做日志)來完成,重做日志負責恢複
  • 一緻性由

    undo log

    來實作
Redo Log

重做日志用來實作事務的持久性,由兩部分組成

  • redo log buffer

    (重做日志緩沖),存在于記憶體中
  • redo log file

    (重做日志檔案),存在于磁盤上

InnoDB在送出事務時,采用Force Log at Commit機制保證持久性:首先要先将該事務的所有日志寫入到重做日志檔案,并調用

fsync

操作,待事務的

COMMIT

操作完成時才算完成事務。

Redo Log是順序寫的,資料庫運作期間無需對其進行讀取操作。

參數

innodb_flush_log_at_trx_commit

可以控制重做日志重新整理磁盤的政策,預設值為1,表示事務送出時必須調用

fsync

操作,這樣即使是作業系統發生了當機也不會影響事務的持久性。此外還可以将其設定為0和2。當值為0時表示事務送出時不進行寫入重做日志的操作,這麼設定的話相當于失去了事務的持久性。設定為2時表示事務送出時将重做日志寫入到重做日志檔案,但不進行顯式的

fsync

操作,這樣可以在作業系統不發生崩潰、當機的前提下保證事務的持久性。

總的來說,将

innodb_flush_log_at_trx_commit

設定為0或2可以有效地提高InnoDB表寫入的性能,但是事務的持久性得不到保證。

更多有關重做日志的内容可以參考這篇部落格

Undo Log

基本概念

事務的復原操作是基于

undo log

實作的,在事務中,除了會持續寫入重做日志,還會産生

undo

undo

存放在資料庫内部的一個特殊段(segment)中,這個段稱之為

undo

段,這個段存放于共享表空間中。

undo

是邏輯日志,InnoDB根據該日志邏輯地将資料庫内容恢複到事務開始時的樣子,是以資料結構和頁本身在復原之後可能大不相同。InnoDB在實際生産環境中可能要處理成百上千個并發事務,可能就會有别的事務對同一個頁中另外幾行資料進行修改,是以不能将一個頁復原到事務開始前的樣子。例如:使用者在一個事務中執行了多個

INSERT

指令後,表空間可能會是以增大,當使用者復原該事務時,表空間并不會是以而變小,它的復原操作執行的是相反的操作,例如

INSERT

改為

DELETE

UPDATE

改為相反的

UPDATE

除了用于復原操作,

undo log

還被用來實作MVCC。MVCC用于保障事務的隔離性:當使用者讀取一行記錄時,如果該記錄已經被其他事務修改并且尚未送出,那麼該使用者可通過

undo

讀取該事務開始之前的行資料。

undo

作為表空間中的一個段,它的産生同樣也會伴随着重做日志的産生,因為

undo

也是需要保證其持久性的。

存儲管理

在共享表空間中,每個

rollback segment

記錄了1024個

undo log segment

,在每個

undo log segment

中進行

undo

頁的申請。共享表空間偏移量為5的頁記錄了所有的

rollback segment header

所在的頁,這個頁的類型為

FIL_PAGE_TYPE_SYS

InnoDB存儲引擎預設支援128個

rollback_segment

,是以支援同時線上的事務限制提高到了128 * 1024。

InnoDB支援以下

undo

相關的參數

  • innodb_undo_directory

    ,用于設定

    rollback_segment

    檔案所在路徑,是以

    rollback segment

    可以存放在獨立表空間中。預設值為

    .

  • innodb_undo_logs

    rollback segment

    的數量上限,預設128
  • innodb_undo_tablespaces

    ,構成

    rollback segment

    的檔案數量,可以将

    rollback segment

    拆分為多個檔案。預設為3。
  • innodb_undo_log_truncate

    ,是否開啟線上回收(收縮)

    undo log

    ,預設為OFF。
mysql> show variables like 'innodb_undo_%';
+--------------------------+-------+
| Variable_name            | Value |
+--------------------------+-------+
| innodb_undo_directory    | .\    |
| innodb_undo_log_truncate | OFF   |
| innodb_undo_logs         | 128   |
| innodb_undo_tablespaces  | 0     |
+--------------------------+-------+
4 rows in set
           

事務送出時,InnoDB會完成以下兩個事件:

  1. undo log

    放入清單,供之後的

    purge

    操作。這是因為事務送出後不能立即删除

    undo log

    undo log

    所在頁。因為可能還會有其它事務需要通過

    undo log

    來獲得舊版本的行記錄,是以将其放置于清單,最終能否删除由

    purge

    線程決定。
  2. 判斷

    undo log

    所在的頁是否能夠重用,若可以配置設定給下個事務使用。因為為每個事務配置設定一個單獨的

    undo

    頁很浪費空間。具體實作是,在

    undo log

    放傳入連結表後,然後判斷

    undo

    頁的使用空間是否小于75%,如果是則表示可以繼續被重用,之後新的

    undo log

    記錄在目前

    undo log

    後面。由于存放

    undo log

    的清單是以記錄的形式組織的,

    undo

    頁也存放着不同僚務的

    undo log

    ,是以

    purge

    操作需要涉及到硬碟的随機讀取操作,相對來說較緩慢。

格式

Undo Log分為兩種類型:

  • insert undo log

    :是指在

    insert

    操作中産生的

    undo log

    。因為事務隔離性的要求:

    insert

    本身的記錄隻能對事務本身可見,對其它事務不可見,故該

    undo log

    可以在事務送出後直接删除,無需進行

    purge

    操作。
  • update undo log

    :記錄的是對

    delete

    update

    操作産生的

    undo log

    。該

    undo log

    需要提供MVCC機制,是以不能在事務送出後就删除。是以需要在送出時放入

    undo log

    連結清單,等待

    purge

    線程進行處理。

下面是

insert undo log

update undo log

的結構圖(

*

表示對字段進行了壓縮):

InnoDB存儲引擎事務ACID的實作

insert undo log

前2個位元組

next

記錄了下一個

undo log

的位置,尾部兩個位元組記錄了目前

undo log

開始的位置。

type_cmpl

占用1個位元組,記錄了

undo

類型,對于

insert undo log

,其值為11。

undo no

記錄了事務ID,

table id

記錄了

undo log

所對應的表。接着的部分記錄了所有主鍵的列和值,進行復原操作時根據這些記錄可以快速定位到具體的行,并進行删除。

update undo log

的結構比

insert undo log

複雜,記錄的内容更多,所需空間也就更大。

next

start

undo no

table id

的意義和

insert undo log

相同。至于

type_cmpl

,因為

update undo log

還可以劃分為3種類型,其值有所不同:

  • 12,即

    TRX_UNDO_UPD_EXIST_REC

    ,更新

    non-delete-mark

    的記錄
  • 13,即

    TRX_UNDO_UPD_DEL_REC

    ,将

    delete

    記錄标記為

    not delete

  • 14,即

    TRX_UNDO_DEL_MARK_REC

    ,将記錄标記為

    delete

update_vector

表示

update

操作導緻發生改變的列。每個修改的列資訊都要記錄在

undo log

中。

Purge

為了友善示範,我們建立一個表

t

,并插入一些資料:

CREATE TABLE t (a INT, b INT, PRIMARY KEY(a), KEY(b))
INSERT INTO t SELECT 1, 1
INSERT INTO t SELECT 3, 1
INSERT INTO t SELECT 5, 3
INSERT INTO t SELECT 7, 6
INSERT INTO t SELECT 10, 8
           

其中列

a

為主鍵,并給列

b

添加了輔助索引。

接下來執行一個删除指令:

該指令會将主鍵列等于1的記錄

delete flag

設定為1,此時記錄并沒有真正被删除,依然存在于聚簇索引中,輔助索引中的記錄同樣存在。真正執行删除的是

purge

線程。

purge

線程被設計成用于完成

delete

update

操作。因為InnoDB支援MVCC機制,是以記錄不能在事務送出後立刻處理,因為其它事務可能正在引用這行,需要儲存該行的舊版本記錄。隻有該行不被其他任何事務引用,那麼才能執行真正的

delete

操作。

參數

innodb_purge_batch_size

可以設定每次

purge

操作需要清理的

undo

頁數量:

mysql> show variables like 'innodb_purge_batch_size';
+-------------------------+-------+
| Variable_name           | Value |
+-------------------------+-------+
| innodb_purge_batch_size | 300   |
+-------------------------+-------+
1 row in set
           

該值預設為300。一般來說,該值設定得越大,每次

purge

操作回收的

undo

頁頁就越多,這樣可重用的

undo

頁就越多,減少了磁盤存儲空間使用和配置設定過程造成的時間開銷。如果設定得過大,那麼可能會導緻資料庫過于集中對于

undo log

的處理,造成性能的下降。一般來說普通使用者無需調整該參數。

Group Commit

預設情況下,非隻讀事務在送出時都需要進行一次

fsync

操作。為了提高

fsync

的效率,InnoDB提供了

group commit

機制,即一次

fsync

操作就可以確定将多個事務的重做日志寫入到檔案,具體來說分為兩個步驟:

  1. 修改記憶體中事務對應的資訊,并且将日志寫入到重做日志緩沖
  2. 調用

    fsync

    確定日志都從重做日志緩沖寫入到了磁盤

fsync

是一個較為緩慢的操作,因為涉及到了磁盤IO的性能。通過合并多個事務的重做日志然後僅調用一次

fsync

操作來完成重做日志的持久化,是提高資料庫性能的有效方式,能夠大大減少磁盤的壓力。

MySQL 5.6之後的版本采用了稱之為

Binary Log Group Commit

(BLGC)的方式來實作

group commit

。在送出事務時,首先按順序将事務放到一個隊列中,隊列中的第一個事務稱之為

leader

,其它事務稱之為

follower

leader

控制

follower

的行為。

BLGC将事務送出的過程分為了三個階段:

  1. Flush階段,将每個事務的二進制日志寫入到記憶體中(二進制日志緩沖)
  2. Sync階段,将二進制日志緩沖内容重新整理到磁盤,若隊列有多個事務,那麼僅調用一次

    fsync

    就能夠完成二進制日志的寫入,這就是BLGC
  3. Commit階段,

    leader

    事務根據順序調用存儲引擎層事務的送出(InnoDB本身支援

    group commit

    ,也就是上面那兩個步驟)。

當有一組事務進行Commit的同時,其它事務也可以進行Flush操作,進而使

group commit

不停地生效。

group commit

的性能提升效果由隊列中的事務數量決定。如果每次事務隊列中僅有一個事務,那麼性能可能反而會變差。

參考資料

《MySQL技術内幕(InnoDB存儲引擎)》