MySQL?InnoDB?undo?log數(shù)據(jù)結(jié)構(gòu)用法詳解
一、概念解析
1、undo log基本
undo log是InnoDB事務(wù)中特有的結(jié)構(gòu),它的作用有兩個(gè):
- 一是進(jìn)行事務(wù)回滾(原子性),舊數(shù)據(jù)先放到undo log中,等rollback時(shí)再將舊數(shù)據(jù)里的數(shù)據(jù)回滾回來(lái);
- 二是MVCC(隔離性),非阻塞讀。
undo能夠避免臟讀:在操作數(shù)據(jù)之前,將數(shù)據(jù)備份到undo頁(yè)中,然后在進(jìn)行數(shù)據(jù)的修改,不能看到其他回話(huà)未提交的數(shù)據(jù),當(dāng)要讀取被修改頁(yè)數(shù)據(jù)行時(shí),會(huì)指向備份在undo頁(yè)中的數(shù)據(jù),避免臟讀。
- undo回滾段頭物理存儲(chǔ)在ibdata1文件中第6個(gè)頁(yè)位置。
- undo log存放在數(shù)據(jù)庫(kù)內(nèi)部的一個(gè)特殊段中,這個(gè)段稱(chēng)為undo段。undo段位于共享表空間中。回滾段實(shí)際上是一種 undo 文件組織方式。
在InnoDB存儲(chǔ)引擎中有rollback segment,每個(gè)回滾段記錄了1024個(gè)undo log segment(slot),每個(gè)undo log segment段中進(jìn)行undo頁(yè)的申請(qǐng)。
從1.1版本開(kāi)始InnoDB支持最大128個(gè)rollback segment,所以理論上可以支持的最大的事務(wù)上限為128*1024。
在1.2版本開(kāi)始,可以通過(guò)參數(shù)對(duì)rollback segment進(jìn)行設(shè)置,參數(shù)有:
參數(shù)名稱(chēng) | 含義 |
innodb_undo_directory | 設(shè)置回滾段文件所在的路徑 |
innodb_undo_logs | 設(shè)置回滾段的個(gè)數(shù) |
innodb_undo_tablespaces | 設(shè)置構(gòu)成回滾段文件的數(shù)量 |
也就是說(shuō):
回滾段可以存放在共享表空間以外的地方,即可以設(shè)置獨(dú)立表空間,innodb_undo_directory的默認(rèn)值為“.”,表示當(dāng)前InnoDB存儲(chǔ)引擎的目錄。
回滾段的個(gè)數(shù)默認(rèn)值為128。
設(shè)置回滾段文件的數(shù)量后,回滾段可以較為平均地分布在多個(gè)文件中,在路徑下可以看到undo為前綴的文件,該文件代表回滾段文件。
可用show variables like “innodb_undo%”;查詢(xún):

重新初始化時(shí)指定innodb_undo_tablespaces=N來(lái)設(shè)置,設(shè)置好后會(huì)在data目錄下生成undo獨(dú)立表空間(大小10M):

2、事務(wù)系統(tǒng)頭transactionsystemheader
回滾段的管理,在InnoDB共享表空間中,用第6個(gè)頁(yè)面(5號(hào))來(lái)管理的,這個(gè)頁(yè)面專(zhuān)門(mén)用來(lái)存儲(chǔ)事務(wù)系統(tǒng)相關(guān)信息的,源代碼位于trx0sys.h,它的格式如下:

這三個(gè)參數(shù)的含義分別為:
參數(shù)名稱(chēng) | 含義 |
TRX_SYS_TRX_ID_STORE | 存儲(chǔ)事務(wù)號(hào) |
TRX_SYS_FSEG_HEADER | 存儲(chǔ)事務(wù)段信息 |
TRX_SYS_RSEGS | 數(shù)組,InnoDB有128個(gè)rollback segment |
3、回滾段頭rollbacksegmentheader
對(duì)于每一個(gè)回滾段,即上面TRX_SYS_RSEGS數(shù)組中的一個(gè)元素,也有其自己的存儲(chǔ)格式,代碼中的宏定義位于trx0rseg.h,格式如下:

這五個(gè)信息的含義如下:
參數(shù)名稱(chēng) | 含義 |
TRX_RSEG_MAX_SIZE | 所有undo段頁(yè)面之和 |
TRX_RSEG_HISTORY_SIZE | 需要purge的回滾段頁(yè)面數(shù) |
TRX_RSEG_HISTORY | 存儲(chǔ)history list的鏈表首地址 |
TRX_RSEG_FSEG_HEADER | 存儲(chǔ)回滾段的inode位置信息 |
TEX_RSEG_UNDO_SLOTS | 數(shù)組,1024,每個(gè)元素是一個(gè)頁(yè)面號(hào) |
這五個(gè)信息存儲(chǔ)了一個(gè)回滾段的信息,最后一個(gè)位置的數(shù)組,用來(lái)真正存儲(chǔ)回滾段的位置。
這五個(gè)信息是從頁(yè)面偏移38的位置開(kāi)始存儲(chǔ),在TRX_SYS宏定義中可以查看:



4、回滾頁(yè)頭undologpageheader
定義在trx0undo.h,事務(wù)undo日志頁(yè)面頭偏移量:

其中各個(gè)參數(shù)的含義為:
參數(shù)名稱(chēng) | 含義 |
TRX_UNDO_PAGE_TYPE | 類(lèi)型,TRX_UNDO_INSERT\UPDATE |
TRX_UNDO_PAGE_START | 開(kāi)始存儲(chǔ)undo日志的位置 |
TRX_UNDO_PAGE_FREE | 結(jié)束標(biāo)志 |
TRX_UNDO_PAGE_NODE | 雙向鏈表的指針 |
第一個(gè)頁(yè)在undo log段的回滾日志段頭:

其中各個(gè)參數(shù)的含義為:
參數(shù)名稱(chēng) | 含義 |
TRX_UNDO_STATE | undo段五種狀態(tài),包括TRX_UNDO_ACTIVE\CACHED\TO_FREE\TO_PURGE\PREPARED |
TRX_UNDO_LAST_LOG | 最后一個(gè)undo日志的偏移位置 |
TRX_UNDO_FSEG_HEADER | undo段inode信息 |
TRX_UNDO_PAGE_LIST | 段鏈表的首地址 |
其中undo log段的狀態(tài)有五種:

5、回滾日志頭undologheader
這是回滾日志的頭,在第一頁(yè)的update undo log中有幾個(gè)undo log headers。同樣定義在trx0undo.h中,它的宏定義如下:

整理并解釋其中的含義:
參數(shù)名稱(chēng) | 含義 |
TRX_UNDO_TRX_ID | 存儲(chǔ)當(dāng)前undo日志對(duì)應(yīng)事務(wù)的事務(wù)id |
TRX_UNDO_TRX_NO | 事務(wù)序列號(hào) |
TRX_UNDO_DEL_MARKS | 刪除記錄標(biāo)記 |
TRX_UNDO_LOG_START | 存儲(chǔ)當(dāng)前頁(yè)面中第一個(gè)undo日志的開(kāi)始位置 |
TRX_UNDO_XID_EXISTS | 標(biāo)記的當(dāng)前日 志中有沒(méi)有包含xid事務(wù) |
TRX_UNDO_DICT_TRANS | 標(biāo)記當(dāng)前事務(wù)是不是DDL,在回滾時(shí)判斷如何操作 |
TRX_UNDO_TABLE_ID | 表id |
TRX_UNDO_NEXT_LOG | 鏈接指向下一個(gè)undo日志 |
TRX_UNDO_PREV_LOG | 指向上一個(gè)undo日志,構(gòu)成雙向鏈表 |
TRX_UNDO_HISTORY_NODE | 存儲(chǔ)history list中的雙向鏈表,鏈表首地址 |
6、兩種undolog格式
undo log格式有兩種:
- insert undo log
- update undo log

其中,insert操作會(huì)產(chǎn)生insert undo log。因?yàn)楦綦x性的要求,insert操作只對(duì)事務(wù)本身可見(jiàn),對(duì)其他事務(wù)不可見(jiàn)。并且undo log可以在事務(wù)提交后直接刪除,不需要進(jìn)行purge操作。
update和delete操作都會(huì)產(chǎn)生update undo log。對(duì)于update操作很簡(jiǎn)單,就是將舊版本的值修改為新版本的值,并且將舊版本的記錄保存到undo log中;對(duì)于delete操作完后并不是真正的刪除該行數(shù)據(jù),而是將該行的數(shù)據(jù)存在undo log中,并且將標(biāo)記位delete置為true,以備回滾時(shí)可以根據(jù)標(biāo)記位進(jìn)行逆操作,這樣就能夠找回之前的舊數(shù)據(jù),起到回滾的作用。
在delete中又分為三種情況:

purge線程兩個(gè)主要作用是:清理undo頁(yè)和清除page里面帶有delete_bit標(biāo)識(shí)的數(shù)據(jù)行。在InnoDB中,事務(wù)中的delete操作實(shí)際上并不是真正的刪除掉數(shù)據(jù)行,而是在記錄上標(biāo)識(shí)delete_bit,而不是真正刪除記錄,真正的刪除工作需要后臺(tái)purge線程去完成。
下面是兩種undo log記錄的具體的結(jié)構(gòu):

二、數(shù)據(jù)結(jié)構(gòu)
與undo log相關(guān)的數(shù)據(jù)結(jié)構(gòu)有trx_sys_t、trx_rsegs_t 、trx_t、trx_rseg_t、trx_undo_t、trx_undo_ptr_t等結(jié)構(gòu),其中比較重要的結(jié)構(gòu)有兩個(gè):trx_rseg_t和trx_undo_t。trx_rseg_t保存回滾段的信息,trx_undo_t保存關(guān)于回滾日志的信息。

通過(guò)兩種方式找到回滾日志:


結(jié)論:
1、從全局變量trx_sys中的回滾段數(shù)組rseg_array中的回滾鏈表update-undo_list中指針(trx_sys->rseg_array[N]->update_undo_list->start)以及當(dāng)前事務(wù)trx中的回滾段指針rsegs中的slot槽區(qū)m_redo中的undo log(trx->rsegs->m_redo->update_undo)都可以找到相應(yīng)的回滾段中的回滾日志。
2、事務(wù)id、回滾段id、page_no遞增。
1、trx_rseg_t
回滾段的內(nèi)存對(duì)象的類(lèi)型是一個(gè)叫trx_rseg_t的結(jié)構(gòu),它定義在trx0rseg.h文件中,它的各成員有:
成員變量 | 解釋 |
ulint id; | 回滾段id |
RsegMutex mutex; | 回滾段mutex,保護(hù) |
ulint space; | 回滾段頭空間 |
ulint page_no; | 回滾段頁(yè)碼 |
page_size_t page_size; | 頁(yè)面大小 |
ulint max_size; | 允許的最大頁(yè)大小 |
ulint curr_size; | 當(dāng)前頁(yè)的大小 |
UT_LIST_BASE_NODE_T(trx_undo_t) update_undo_list; | update回滾日志鏈表 |
UT_LIST_BASE_NODE_T(trx_undo_t) update_undo_cached; | 為快速重用而緩存的update回滾日志鏈表 |
UT_LIST_BASE_NODE_T(trx_undo_t) insert_undo_list; | insert回滾日志的字段 |
UT_LIST_BASE_NODE_T(trx_undo_t) insert_undo_cached; | 為快速重用而緩存的insert undo log鏈表 |
ulint last_page_no; | 最后一個(gè)尚未清除的日志頁(yè)碼 |
ulint last_offset; | 最后一個(gè)尚未清除的日志頭的字節(jié)偏移量 |
trx_id_t last_trx_no; | 最后一個(gè)尚未清除的日志的事務(wù)號(hào) |
ibool last_del_marks; | 判斷最后一個(gè)尚未清除的日志是否要清除 |
ulint trx_ref_count; | 跟蹤rseg分配事務(wù)的參考計(jì)數(shù)器 |
bool skip_allocation; | 如果為真,則跳過(guò)分配此rseg |
update\insert_undo_list 用于保存產(chǎn)生的回滾日志,update\insert_undo_cached用于快速重用而緩存的回滾日志鏈表。當(dāng)確定回滾日志無(wú)用時(shí),會(huì)將回滾日志從update\insert_undo_list中摘除,放到相應(yīng)的cached鏈表中。
2、trx_undo_t
回滾日志的內(nèi)存對(duì)象的類(lèi)型是一個(gè)叫trx_undo_t的結(jié)構(gòu),它定義在trx0undo.h文件中,它的各成員有:
成員變量 | 解釋 |
ulint id; | 回滾段內(nèi)的回滾日志槽號(hào)(slot) |
ulint type; | 類(lèi)型。TRX_UNDO_INSERT\UPDATE |
ulint state; | 相應(yīng)的回滾日志段的狀態(tài) |
ibool del_marks; | 刪除標(biāo)記 |
trx_id_t trx_id; | 事務(wù)id |
XID xid; | open XA事務(wù)識(shí)別 |
ibool dict_operation; | 是否是dict操作 |
table_id_t table_id; | 設(shè)置表ID |
trx_rseg_t* rseg; | 回滾日志所屬的rseg回滾段 |
ulint space; | 放置回滾日志的空間id |
page_size_t page_size; | 回滾日志中標(biāo)題頁(yè)的大小 |
ulint hdr_page_no; | 回滾日志中標(biāo)題頁(yè)的頁(yè)碼 |
ulint hdr_offset; | 頁(yè)面上回滾日志的header偏移量 |
ulint last_page_no; | 登錄日志中最后一頁(yè)的頁(yè)碼 |
ulint size; | 當(dāng)前頁(yè)的大小 |
ulint empty; | 回滾日志記錄堆棧當(dāng)前是否為空 |
ulint top_page_no; | 鏈接最近撤消日志記錄的頁(yè)碼 |
ulint top_offset; | 最新回滾記錄的偏移量 |
undo_no_t top_undo_no; | 最新的回滾記錄的編號(hào) |
buf_block_t* guess_block; | 猜測(cè)可能位于首頁(yè)的緩沖區(qū) |
ulint withdraw_clock; | 存儲(chǔ)guess_block時(shí)緩沖池的退出時(shí)鐘值 |
UT_LIST_NODE_T(trx_undo_t) undo_list; | 回滾段中的回滾日志對(duì)象鏈表 |
回滾段指針包括回滾段的id、日志所在的page no、以及page內(nèi)偏移量。通過(guò)回滾段的指針就能夠找到這個(gè)回滾段,通過(guò)回滾日志的槽號(hào)、頁(yè)碼、回滾記錄編號(hào)和偏移量就能定位到這條回滾日志。
undo_list用于將此類(lèi)型的undo log鏈接到undo log list中。
UT_LIST_NODE_T(trx_undo_t) undo_list鏈表中有兩個(gè)指針,分別為prev和next,用于將此undo log鏈接到undo log list中。
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
Java實(shí)現(xiàn)獲得MySQL數(shù)據(jù)庫(kù)中所有表的記錄總數(shù)可行方法
可以通過(guò)SELECT COUNT(*) FROM table_name查詢(xún)某個(gè)表中有多少條記錄。本文給出兩種可行的Java程序查詢(xún)所有別的記錄方法,感興趣朋友可以了解下2013-06-06
Mysql 默認(rèn)字符集設(shè)置方法(免安裝版)
有些時(shí)候我們?cè)谑褂梅前惭b版的mysql是需要設(shè)置默認(rèn)字符集的時(shí)候,就需要這樣的修改了。安裝版的可以選擇的。2009-03-03
遠(yuǎn)程連接mysql數(shù)據(jù)庫(kù)注意點(diǎn)記錄
有時(shí)候我們需要遠(yuǎn)程連接mysql數(shù)據(jù)庫(kù),那么就需要注意如下問(wèn)題,需要的朋友可以參考下2012-08-08
簡(jiǎn)單談?wù)凪ySQL的loose index scan
各種資料關(guān)于loose index scan的解釋很拗口,其實(shí)等同于oracle的index skip scan。今天我們就來(lái)詳細(xì)看看loose index scan的使用2015-12-12
MySQL關(guān)聯(lián)查詢(xún)Join的實(shí)現(xiàn)原理和優(yōu)化建議
在平時(shí)的開(kāi)發(fā)過(guò)程中,大家應(yīng)該經(jīng)常會(huì)用到j(luò)oin進(jìn)行多個(gè)表的之間的關(guān)聯(lián)查詢(xún),但是如果使用不合理的話(huà),會(huì)導(dǎo)致查詢(xún)性能下降,本文就MySQL中的關(guān)聯(lián)查詢(xún)的實(shí)現(xiàn)原理以及一些優(yōu)化建議等內(nèi)容做一個(gè)講解,需要的朋友可以參考下2023-07-07
mysql復(fù)制data文件遷移的實(shí)現(xiàn)步驟
有時(shí)候,我們需要遷移整個(gè)數(shù)據(jù)庫(kù),包括數(shù)據(jù)文件,本文將介紹如何通過(guò)復(fù)制MySQL的data文件來(lái)完成數(shù)據(jù)庫(kù)遷移,具有一定的參考價(jià)值,感興趣的可以了解一下2023-11-11
mysql 根據(jù)時(shí)間范圍查詢(xún)數(shù)據(jù)的操作方法
這篇文章主要介紹了mysql 根據(jù)時(shí)間范圍查詢(xún)數(shù)據(jù)的操作方法,下面是一些常見(jiàn)的時(shí)間范圍查詢(xún)示例代碼,需要的朋友可以參考下2024-01-01

