閱讀821 返回首頁    go 阿裏雲 go 技術社區[雲棲]


MySQL · 引擎特性 · InnoDB mini transation

前言

InnoDB有兩個非常重要的日誌,undo log 和 redo log;通過undo log可以看到數據較早版本,實現MVCC,或回滾事務等功能;redo log用來保證事務持久性

本文以一條insert語句為線索介紹 mini transaction

mini transaction 簡介

mini transation 主要用於innodb redo log 和 undo log寫入,保證兩種日誌的ACID特性

mini-transaction遵循以下三個協議:

  1. The FIX Rules

  2. Write-Ahead Log

  3. Force-log-at-commit

The FIX Rules

修改一個頁需要獲得該頁的x-latch

訪問一個頁是需要獲得該頁的s-latch或者x-latch

持有該頁的latch直到修改或者訪問該頁的操作完成

Write-Ahead Log

持久化一個數據頁之前,必須先將內存中相應的日誌頁持久化

每個頁有一個LSN,每次頁修改需要維護這個LSN,當一個頁需要寫入到持久化設備時,要求內存中小於該頁LSN的日誌先寫入到持久化設備中

Force-log-at-commit

一個事務可以同時修改了多個頁,Write-AheadLog單個數據頁的一致性,無法保證事務的持久性

Force -log-at-commit要求當一個事務提交時,其產生所有的mini-transaction日誌必須刷到持久設備中

這樣即使在頁數據刷盤的時候宕機,也可以通過日誌進行redo恢複

代碼簡介

本文使用 MySQL 5.6.16 版本進行分析

mini transation 相關代碼路徑位於 storage/innobase/mtr/ 主要有 mtr0mtr.cc 和 mtr0log.cc 兩個文件

另有部分代碼在 storage/innobase/include/ 文件名以 mtr0 開頭

mini transaction 的信息保存在結構體 mtr_t 中,結構體成員描述如下

成員屬性 描述
state mini transaction所處狀態 MTR_ACTIVE, MTR_COMMITTING, MTR_COMMITTED
memo mtr 持有鎖的棧
log mtr產生的日誌
inside_ibuf insert buffer 是否修改
modifications 是否修改buffer pool pages
made_dirty 是否產生buffer pool髒頁
n_log_recs log 記錄數
n_freed_pages 釋放page數
log_mode 日誌模式,默認MTR_LOG_ALL
start_lsn lsn 起始值
end_lsn lsn 結束值
magic_n 魔術字

一個 mini transaction 從 mtr_start(mtr)開始,到 mtr_commit(mtr)結束

一條insert語句涉及的 mini transaction

下麵涉及 mtr 的嵌套,在代碼中,每個 mtr_t 對象變量名都叫 mtr,本文中為了區分不同 mtr,給不同的對象加編號

下麵一般省略 mtr_t 以外的參數

第一個 mtr 從 row_ins_clust_index_entry_low 開始

mtr_start(mtr_1) // mtr_1 貫穿整條insert語句
row_ins_clust_index_entry_low


mtr_s_lock(dict_index_get_lock(index), mtr_1) // 對index加s鎖
btr_cur_search_to_nth_level
row_ins_clust_index_entry_low


mtr_memo_push(mtr_1) // buffer RW_NO_LATCH 入棧
buf_page_get_gen
btr_cur_search_to_nth_level
row_ins_clust_index_entry_low


mtr_memo_push(mtr_1) // page RW_X_LATCH 入棧
buf_page_get_gen
btr_block_get_func
btr_cur_latch_leaves
btr_cur_search_to_nth_level
row_ins_clust_index_entry_low

	
	mtr_start(mtr_2) // mtr_2 用於記錄 undo log
	trx_undo_report_row_operation
	btr_cur_ins_lock_and_undo
	btr_cur_optimistic_insert
	row_ins_clust_index_entry_low
	
	
		mtr_start(mtr_3) // mtr_3 分配或複用一個 undo log
		trx_undo_assign_undo
		trx_undo_report_row_operation
		btr_cur_ins_lock_and_undo
		btr_cur_optimistic_insert
		row_ins_clust_index_entry_low
		
		mtr_memo_push(mtr_3) // 對複用(也可能是分配)的 undo log page 加 RW_X_LATCH 入棧
		buf_page_get_gen
		trx_undo_page_get
		trx_undo_reuse_cached // 這裏先嚐試複用,如果複用失敗,則分配新的 undo log
		trx_undo_assign_undo
		trx_undo_report_row_operation
		

 		trx_undo_insert_header_reuse(mtr_3) // 寫 undo log header
		trx_undo_reuse_cached
		trx_undo_assign_undo
		trx_undo_report_row_operation
		
		
		trx_undo_header_add_space_for_xid(mtr_3) // 在 undo header 中預留 XID 空間
		trx_undo_reuse_cached
		trx_undo_assign_undo
		trx_undo_report_row_operation
		
		
		mtr_commit(mtr_3) // 提交 mtr_3
		trx_undo_assign_undo
		trx_undo_report_row_operation
		btr_cur_ins_lock_and_undo
		btr_cur_optimistic_insert
		row_ins_clust_index_entry_low
	
	mtr_memo_push(mtr_2) // 即將寫入的 undo log page 加 RW_X_LATCH 入棧
	buf_page_get_gen
	trx_undo_report_row_operation
	btr_cur_ins_lock_and_undo
	btr_cur_optimistic_insert
	row_ins_clust_index_entry_low
	
	
	trx_undo_page_report_insert(mtr_2) // undo log 記錄 insert 操作
	trx_undo_report_row_operation
	btr_cur_ins_lock_and_undo
	btr_cur_optimistic_insert
	row_ins_clust_index_entry_low
	
	
	mtr_commit(mtr_2) // 提交 mtr_2
	trx_undo_report_row_operation
	btr_cur_ins_lock_and_undo
	btr_cur_optimistic_insert
	row_ins_clust_index_entry_low
	
/*
	mtr_2 提交後開始執行 insert 操作
	page_cur_insert_rec_low 具體執行 insert 操作
	在該函數末尾調用 page_cur_insert_rec_write_log 寫 redo log
*/


page_cur_insert_rec_write_log(mtr_1) // insert 操作寫 redo log
page_cur_insert_rec_lowpage_cur_tuple_insert
btr_cur_optimistic_insert


mtr_commit(mtr_1) // 提交 mtr_1
row_ins_clust_index_entry_low	

至此 insert 語句執行結束後

一條 insert 是一個單語句事務,事務提交時也會涉及 mini transaction

提交事務時,第一個 mtr 從 trx_prepare 開始

mtr_start(mtr_4) // mtr_4 用於 prepare transaction
trx_prepare
trx_prepare_for_mysql
innobase_xa_prepare
ha_prepare_low
MYSQL_BIN_LOG::prepare
ha_commit_trans
trans_commit_stmt
mysql_execute_command


mtr_memo_push(mtr_4) // undo page 加 RW_X_LATCH 入棧
buf_page_get_gen
trx_undo_page_get
trx_undo_set_state_at_prepare
trx_prepare


mlog_write_ulint(seg_hdr + TRX_UNDO_STATE, undo->state, MLOG_2BYTES, mtr_4) 寫入TRX_UNDO_STATE
trx_undo_set_state_at_prepare
trx_prepare


mlog_write_ulint(undo_header + TRX_UNDO_XID_EXISTS, TRUE, MLOG_1BYTE, mtr_4) 寫入 TRX_UNDO_XID_EXISTS
trx_undo_set_state_at_prepare
trx_prepare


trx_undo_write_xid(undo_header, &undo->xid, mtr_4) undo 寫入 xid
trx_undo_set_state_at_prepare
trx_prepare


mtr_commit(mtr_4) // 提交 mtr_4
trx_prepare




mtr_start(mtr_5) // mtr_5 用於 commit transaction
trx_commit
trx_commit_for_mysql
innobase_commit_low
innobase_commit
ha_commit_low
MYSQL_BIN_LOG::process_commit_stage_queue
MYSQL_BIN_LOG::ordered_commit
MYSQL_BIN_LOG::commit
ha_commit_trans
trans_commit_stmt
mysql_execute_command



mtr_memo_push(mtr_5) // undo page 加 RW_X_LATCH 入棧
buf_page_get_gen
trx_undo_page_get
trx_undo_set_state_at_finish
trx_write_serialisation_history
trx_commit_low
trx_commit


trx_undo_set_state_at_finish(mtr_5) // set undo state, 這裏是 TRX_UNDO_CACHED
trx_write_serialisation_history
trx_commit_low
trx_commit


mtr_memo_push(mtr_5) // 係統表空間 transaction system header page 加 RW_X_LATCH 入棧
buf_page_get_gen
trx_sysf_get
trx_sys_update_mysql_binlog_offset
trx_write_serialisation_history
trx_commit_low
trx_commit


trx_sys_update_mysql_binlog_offset // 更新偏移量信息到係統表空間
trx_write_serialisation_history
trx_commit_low
trx_commit

mtr_commit(mtr_5) // 提交 mtr_5
trx_commit_low
trx_commit

至此 insert 語句涉及的 mini transaction 全部結束

總結

上麵可以看到加鎖、寫日誌到 mlog 等操作在 mini transaction 過程中進行

解鎖、把日誌刷盤等操作全部在 mtr_commit 中進行,和事務類似

mini transaction 沒有回滾操作, 因為隻有在 mtr_commit 才將修改落盤,如果宕機,內存丟失,無需回滾;如果落盤過程中宕機,崩潰恢複時可以看出落盤過程不完整,丟棄這部分修改

mtr_commit 主要包含以下步驟

  1. mlog 中日誌刷盤
  2. 釋放 mtr 持有的鎖,鎖信息保存在 memo 中,以棧形式保存,後加的鎖先釋放
  3. 清理 mtr 申請的內存空間,memo 和 log
  4. mtr—>state 設置為 MTR_COMMITTED

上麵的步驟 1. 中,日誌刷盤策略和 innodb_flush_log_at_trx_commit 有關

  • 當設置該值為1時,每次事務提交都要做一次fsync,這是最安全的配置,即使宕機也不會丟失事務
  • 當設置為2時,則在事務提交時隻做write操作,隻保證寫到係統的page cache,因此實例crash不會丟失事務,但宕機則可能丟失事務
  • 當設置為0時,事務提交不會觸發redo寫操作,而是留給後台線程每秒一次的刷盤操作,因此實例crash將最多丟失1秒鍾內的事務

最後更新:2017-10-21 09:03:56

  上一篇:go  MySQL · 特性介紹 · 一些流行引擎存儲格式簡介
  下一篇:go  MySQL · 性能優化· CloudDBA SQL優化建議之統計信息獲取