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
的數量上限,預設128rollback segment
-
,構成innodb_undo_tablespaces
的檔案數量,可以将rollback segment
拆分為多個檔案。預設為3。rollback segment
-
,是否開啟線上回收(收縮)innodb_undo_log_truncate
,預設為OFF。undo log
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會完成以下兩個事件:
- 将
放入清單,供之後的undo log
操作。這是因為事務送出後不能立即删除purge
和undo log
所在頁。因為可能還會有其它事務需要通過undo log
來獲得舊版本的行記錄,是以将其放置于清單,最終能否删除由undo log
線程決定。purge
- 判斷
所在的頁是否能夠重用,若可以配置設定給下個事務使用。因為為每個事務配置設定一個單獨的undo log
頁很浪費空間。具體實作是,在undo
放傳入連結表後,然後判斷undo log
頁的使用空間是否小于75%,如果是則表示可以繼續被重用,之後新的undo
記錄在目前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
需要提供MVCC機制,是以不能在事務送出後就删除。是以需要在送出時放入undo log
連結清單,等待undo log
線程進行處理。purge
下面是
insert undo log
和
update undo log
的結構圖(
*
表示對字段進行了壓縮):
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
操作就可以確定将多個事務的重做日志寫入到檔案,具體來說分為兩個步驟:
- 修改記憶體中事務對應的資訊,并且将日志寫入到重做日志緩沖
- 調用
確定日志都從重做日志緩沖寫入到了磁盤fsync
fsync
是一個較為緩慢的操作,因為涉及到了磁盤IO的性能。通過合并多個事務的重做日志然後僅調用一次
fsync
操作來完成重做日志的持久化,是提高資料庫性能的有效方式,能夠大大減少磁盤的壓力。
MySQL 5.6之後的版本采用了稱之為
Binary Log Group Commit
(BLGC)的方式來實作
group commit
。在送出事務時,首先按順序将事務放到一個隊列中,隊列中的第一個事務稱之為
leader
,其它事務稱之為
follower
。
leader
控制
follower
的行為。
BLGC将事務送出的過程分為了三個階段:
- Flush階段,将每個事務的二進制日志寫入到記憶體中(二進制日志緩沖)
- Sync階段,将二進制日志緩沖内容重新整理到磁盤,若隊列有多個事務,那麼僅調用一次
就能夠完成二進制日志的寫入,這就是BLGCfsync
- Commit階段,
事務根據順序調用存儲引擎層事務的送出(InnoDB本身支援leader
,也就是上面那兩個步驟)。group commit
當有一組事務進行Commit的同時,其它事務也可以進行Flush操作,進而使
group commit
不停地生效。
group commit
的性能提升效果由隊列中的事務數量決定。如果每次事務隊列中僅有一個事務,那麼性能可能反而會變差。
參考資料
《MySQL技術内幕(InnoDB存儲引擎)》