前言:MySQL5.7新特性之一介紹了一些新特性及兼容性問題牵舱,MySQL 5.7新特性之二介紹了臨時表的優(yōu)化和實(shí)現(xiàn)历造。
這期我們一起來學(xué)習(xí)下undo空間管理蒲讯,重點(diǎn)介紹truncate功能垫蛆。
1. 背景
InnoDB存儲引擎中违孝,undo在完成事務(wù)回滾和MVCC之后,就可以purge掉了集索,但undo在事務(wù)執(zhí)行過程中屿愚,進(jìn)行的空間分配如何回收,就變成了一個問題抄谐。 我們親歷用戶的小實(shí)例渺鹦,因?yàn)橐粋€大事務(wù),導(dǎo)致ibdata file到800G大小蛹含。
我們先大致看下InnoDB的undo在不同的版本上的一些演進(jìn):
MySQL 5.5的版本上
InnoDB undo是放在系統(tǒng)表空間即ibdata file文件中毅厚,這樣如果有比較大的事務(wù)(即需要生成大量undo的),會撐大ibdata數(shù)據(jù)文件浦箱,
雖然空間可以重用吸耿, 但文件大小不能更改。
關(guān)于回滾段的酷窥,只有這個主要的參數(shù)咽安,用來設(shè)置多少個rollback segment。
mysql> show global variables like '%rollback_segment%';
+----------------------------+-------+
| Variable_name | Value |
+----------------------------+-------+
| innodb_rollback_segments | 128 |
+----------------------------+-------+
MySQL 5.6的版本上
InnoDB undo支持獨(dú)立表空間蓬推, 增加如下參數(shù):
+-------------------------+-------+
| Variable_name | Value |
+-------------------------+-------+
| innodb_undo_directory | . |
| innodb_undo_logs | 128 |
| innodb_undo_tablespaces | 1 |
+-------------------------+-------+
這樣妆棒,在install的時候,就會在data目錄下增加undo數(shù)據(jù)文件沸伏,來組成undo獨(dú)立表空間糕珊,但文件變大之后的空間回收還是成為問題。
MySQL 5.7的版本上
InnoDB undo在支持獨(dú)立表空間的基礎(chǔ)上毅糟,支持表空間的truncate功能红选,增加了如下參數(shù):
mysql> show global variables like '%undo%'; +--------------------------+------------+
| Variable_name | Value |
+--------------------------+------------+
| innodb_max_undo_log_size | 1073741824 |
| innodb_undo_directory | ./ |
| innodb_undo_log_truncate | OFF |
| innodb_undo_logs | 128 |
| innodb_undo_tablespaces | 3 |
+--------------------------+------------+
mysql> show global variables like '%truncate%';
+--------------------------------------+-------+
| Variable_name | Value |
+--------------------------------------+-------+
| innodb_purge_rseg_truncate_frequency | 128 |
| innodb_undo_log_truncate | OFF |
+--------------------------------------+-------+
InnoDB的purge線程,會根據(jù)innodb_undo_log_truncate開關(guān)的設(shè)置姆另,和innodb_max_undo_log_size設(shè)置的文件大小閾值喇肋,以及truncate的頻率來進(jìn)行空間回收和rollback segment的重新初始化。
接下來我們詳細(xì)看下5.7的InnoDB undo的管理:
- undo表空間創(chuàng)建
設(shè)置innodb_undo_tablespaces的個數(shù)迹辐, 在mysql install的時候蝶防,創(chuàng)建指定數(shù)量的表空間。
InnoDB支持128個undo logs明吩,這里特別說明下间学,從5.7開始,innodb_rollback_segments的名字改成了innodb_undo_logs贺喝,但表示的都是回滾段的個數(shù)菱鸥。
從5.7.2開始,其中32個undo logs為臨時表的事務(wù)分配的躏鱼,因?yàn)檫@部分undo不記錄redo氮采,不需要recovery,另外從33-128一共96個是redo-enabled undo染苛。
2.rollback segment的分配如下:
Slot-0: reserved for system-tablespace.
Slot-1....Slot-N: reserved for temp-tablespace.
Slot-N+1....Slot-127: reserved for system/undo-tablespace. */
其中如果是臨時表的事務(wù)鹊漠,需要分配兩個undo logs,其中一個是non-redo undo logs茶行;這部分用于臨時表數(shù)據(jù)的回滾躯概。
另外一個是redo-enabled undo log,是為臨時表的元數(shù)據(jù)準(zhǔn)備的畔师,需要recovery娶靡。
而且, 其中32個rollback segment創(chuàng)建在臨時表空間中看锉,并且臨時表空間中的回滾段在每次server start的時候姿锭,需要重建。
每一個rollback segment可以分配1024個slot伯铣,也就是可以支持96*1024個并發(fā)的事務(wù)同時呻此, 但如果是臨時表的事務(wù),需要占用兩個slot腔寡。
InnoDB undo的空間管理簡圖如下:
undo空間管理
注核心結(jié)構(gòu)說明:
- rseg slot
rseg slot一共128個焚鲜,保存在ibdata系統(tǒng)表空間中,其位置在:
/*!< the start of the array of rollback segment specification slots */
###### #define TRX_SYS_RSEGS (8 + FSEG_HEADER_SIZE)
每一個slot保存著rollback segment header的位置放前。包括space_id + page_no忿磅,占用8個bytes。其宏定義:
/* Rollback segment specification slot offsets */
/*-------------------------------------------------------------*/
#######define TRX_SYS_RSEG_SPACE 0 /* space where the segment
header is placed; starting with
MySQL/InnoDB 5.1.7, this is
UNIV_UNDEFINED if the slot is unused */
#######define TRX_SYS_RSEG_PAGE_NO 4 /* page number where the segment
header is placed; this is FIL_NULL
if the slot is unused */
/* Size of a rollback segment specification slot */
#######define TRX_SYS_RSEG_SLOT_SIZE 8
- rseg header
rseg header在undo表空間中犀斋,每一個rseg包括1024個undo segment slot贝乎,每一個slot保存著undo segment header的位置,包括page_no叽粹,暫用4個bytes览效,因?yàn)閡ndo segment不會跨表空間,所以space_id就沒有必要了虫几。
其宏定義如下:
/* Undo log segment slot in a rollback segment header */
/*-------------------------------------------------------------*/
#define TRX_RSEG_SLOT_PAGE_NO 0 /* Page number of the header page of
an undo log segment */
/*-------------------------------------------------------------*/
/* Slot size */
#define TRX_RSEG_SLOT_SIZE 4
- undo segment header
undo segment header page即段內(nèi)的第一個undo page锤灿,其中包括四個比較重要的結(jié)構(gòu):
undo segment header 進(jìn)行段內(nèi)空間的管理
undo page header page內(nèi)空間的管理,page的類型:FIL_PAGE_UNDO_LOG
undo header 包含undo record的鏈表辆脸,以便安裝事務(wù)的反順序但校,進(jìn)行回滾
undo record 剩下的就是undo記錄了。
- undo段的分配
undo段的分配比較簡單啡氢,其過程如下:
首先是rollback segment的分配:
trx->rsegs.m_redo.rseg = trx_assign_rseg_low(
srv_undo_logs, srv_undo_tablespaces,
TRX_RSEG_TYPE_REDO);
使用round-robin的方式來分配rollback segment
如果有單獨(dú)設(shè)置undo表空間状囱,就不使用system表空間中的undo segment
如果設(shè)置的是truncate的就不分配
一旦分配了术裸,就設(shè)置trx_ref_count,不允許truncate亭枷。
具體代碼參考:
/******************************************************************//**
Get next redo rollback segment. (Segment are assigned in round-robin fashion).
@return assigned rollback segment instance */
static
trx_rseg_t*
get_next_redo_rseg(
/*===============*/
ulong max_undo_logs, /*!< in: maximum number of UNDO logs to use */
ulint n_tablespaces) /*!< in: number of rollback tablespaces */
其次是undo segment的創(chuàng)建:
從rollback segment里邊選擇一個free的slot袭艺,如果沒有,就會報錯叨粘,通常是并發(fā)的事務(wù)太多猾编。
錯誤日志如下:
ib::warn() << "Cannot find a free slot for an undo log. Do"
" you have too many active transactions running"
" concurrently?";
如果有free,就創(chuàng)建一個undo的segment升敲。
核心的代碼如下:
/***************************************************************//**
Creates a new undo log segment in file.
@return DB_SUCCESS if page creation OK possible error codes are:
DB_TOO_MANY_CONCURRENT_TRXS DB_OUT_OF_FILE_SPACE /
static
dberr_t
trx_undo_seg_create(
/================/
trx_rseg_t rseg attribute((unused)),/!< in: rollback segment /
trx_rsegf_t rseg_hdr,/!< in: rollback segment header, page
x-latched /
ulint type, /!< in: type of the segment: TRX_UNDO_INSERT or
TRX_UNDO_UPDATE /
ulint id, /!< out: slot index within rseg header /
page_t undo_page,
/!< out: segment header page x-latched, NULL
if there was an error /
mtr_t mtr) /!< in: mtr */
/* fputs(type == TRX_UNDO_INSERT
? "Creating insert undo log segment\n"
: "Creating update undo log segment\n", stderr); */
slot_no = trx_rsegf_undo_find_free(rseg_hdr, mtr);
if (slot_no == ULINT_UNDEFINED) {
ib::warn() << "Cannot find a free slot for an undo log. Do"
" you have too many active transactions running"
" concurrently?";
return(DB_TOO_MANY_CONCURRENT_TRXS);
}
- undo的truncate
undo的truncate主要由下面兩個參數(shù)控制:innodb_purge_rseg_truncate_frequency答倡,innodb_undo_log_truncate。
- innodb_undo_log_truncate是開關(guān)參數(shù)驴党。
- innodb_purge_rseg_truncate_frequency默認(rèn)128瘪撇,表示purge undo輪詢128次后,進(jìn)行一次undo的truncate港庄。
當(dāng)設(shè)置innodb_undo_log_truncate=ON的時候设江, undo表空間的文件大小,如果超過了innodb_max_undo_log_size攘轩, 就會被truncate到初始大小叉存,但有一個前提,就是表空間中的undo不再被使用度帮。
其主要步驟如下:
- 超過大小了之后歼捏,會被mark truncation,一次會選擇一個
- 選擇的undo不能再分配新給新的事務(wù)
- purge線程清理不再需要的rollback segment
- 等所有的回滾段都釋放了后笨篷,truncate操作瞳秽,使其成為install db時的初始狀態(tài)。
默認(rèn)情況下率翅, 是purge觸發(fā)128次之后练俐,進(jìn)行一次rollback segment的free操作,然后如果全部free就進(jìn)行一個truncate冕臭。
但mark的操作需要幾個依賴條件需要滿足:
- 系統(tǒng)至少得有兩個undo表空間腺晾,防止一個offline后,至少另外一個還能工作
- 除了ibdata里的segment辜贵,還至少有兩個segment可用
- undo表空間的大小確實(shí)超過了設(shè)置的閾值
其核心代碼參考:
/** Iterate over all the UNDO tablespaces and check if any of the UNDO
tablespace qualifies for TRUNCATE (size > threshold).
@param[in,out] undo_trunc undo truncate tracker */
static
void
trx_purge_mark_undo_for_truncate(
undo::Truncate* undo_trunc)
因?yàn)槊醪酰灰阍O(shè)置了truncate = on,MySQL就盡可能的幫你去truncate所有的undo表空間托慨,所以它會循環(huán)的把undo表空間加入到mark列表中鼻由。
最后,循環(huán)所有的undo段,如果所屬的表空間是marked truncate蕉世,就把這個rseg標(biāo)志位不可分配蔼紧,加入到trunc隊(duì)列中,在purge的時候狠轻,進(jìn)行free rollback segment歉井。
注意:
如果是在線庫,要注意影響哈误,因?yàn)楫?dāng)一個undo tablespace在進(jìn)行truncate的時候,不再承擔(dān)undo的分配躏嚎。只能由剩下的undo 表空間的rollback segment接受事務(wù)undo空間請求蜜自。