水平有限,如果有误请指出。

一直以来未对Innodb 的undo进行好好的学习,最近刚好有点时间准备学习一下,通过阿里内核月报和自己看代码的综合总结一下。本文环境:

代码版本 percona 5.7.22参数 innodb_undo_tablespaces = 4 及使用了4个undo tablespace参数 innodb_rollback_segments = 128

本文描述使用如上参数的设置。

一、undo 表空间物理文件的建立

本过程调用函数srv_undo_tablespaces_init进行,栈帧如下:

#0srv_undo_tablespaces_init(create_new_db=true,n_conf_tablespaces=4,n_opened=0x2ef55b0)at/root/mysqlc/percona-server-locks-detail-5.7.22/storage/innobase/srv/srv0start.cc:824#10x0000000001bbd7e0ininnobase_start_or_create_for_mysql()at/root/mysqlc/percona-server-locks-detail-5.7.22/storage/innobase/srv/srv0start.cc:2188#20x00000000019ca74eininnobase_init(p=0x2f2a420)at/root/mysqlc/percona-server-locks-detail-5.7.22/storage/innobase/handler/ha_innodb.cc:4409#30x0000000000f7ec2ainha_initialize_handlerton(plugin=0x2fca110)at/root/mysqlc/percona-server-locks-detail-5.7.22/sql/handler.cc:871#40x00000000015f9edfinplugin_initialize(plugin=0x2fca110)at/root/mysqlc/percona-server-locks-detail-5.7.22/sql/sql_plugin.cc:1252

本过程主要有如下几个步骤:

根据参数innodb_undo_tablespaces 的配置通过调用srv_undo_tablespace_create分别进行文件建立,默认建立的大小为10M:

for(i=0;create_new_db&&i<n_conf_tablespaces;++i)//n_conf_tablespaces为innodb_undo_tablespaces的配置的个数/**DefaultundotablespacesizeinUNIV_PAGEscount(10MB).*/constulintSRV_UNDO_TABLESPACE_SIZE_IN_PAGES=((1024*1024)*10)/UNIV_PAGE_SIZE_DEF;...err=srv_undo_tablespace_create(name,SRV_UNDO_TABLESPACE_SIZE_IN_PAGES);//建立undo文件...

本步骤会有一个注释如下:

/*Createtheundospacesonlyifwearecreatinganewinstance.Wedon'tallowcreatingofnewundotablespacesinanexistinginstance(yet).ThisrestrictionexistsbecausewecheckinseveralplacesforSYSTEMtablespacestobelessthantheminofuserdefinedtablespaceids.Onceweimplementsavingthelocationoftheundotablespacesandtheirspaceidsthisrestrictionwill/shouldbelifted.*/

简单的讲就是建立undo tablespace只能在初始化实例的时候,因为space id已经固定了。

分别对4个undo tablespace调用srv_undo_tablespace_open 其主要调用fil_space_create 和 fil_node_create将新建立的undo tablespace加入Innodb的文件体系。

for(i=0;i<n_undo_tablespaces;++i){....err=srv_undo_tablespace_open(name,undo_tablespace_ids[i]);//打开UNDO文件建立filenode...}分别对4个undo tablespace 进行fsp header初始化

for(i=0;i<n_undo_tablespaces;++i){fsp_header_init(//初始化fspheader明显spaceid已经写入undo_tablespace_ids[i],SRV_UNDO_TABLESPACE_SIZE_IN_PAGES,&mtr);//SRV_UNDO_TABLESPACE_SIZE_IN_PAGES默认的undo大小10MB}

其中fsp_header_init部分代码如下:

mlog_write_ulint(header+FSP_SPACE_ID,space_id,MLOG_4BYTES,mtr);mlog_write_ulint(header+FSP_NOT_USED,0,MLOG_4BYTES,mtr);mlog_write_ulint(header+FSP_SIZE,size,MLOG_4BYTES,mtr);mlog_write_ulint(header+FSP_FREE_LIMIT,0,MLOG_4BYTES,mtr);mlog_write_ulint(header+FSP_SPACE_FLAGS,space->flags,MLOG_4BYTES,mtr);mlog_write_ulint(header+FSP_FRAG_N_USED,0,MLOG_4BYTES,mtr);flst_init(header+FSP_FREE,mtr);flst_init(header+FSP_FREE_FRAG,mtr);flst_init(header+FSP_FULL_FRAG,mtr);flst_init(header+FSP_SEG_INODES_FULL,mtr);flst_init(header+FSP_SEG_INODES_FREE,mtr);

这些都是fsp的内容。

做完这个步骤只是生成了4个大小为10MB的 undo tablespace文件,并且已经加入到Innodb文件体系,但是里面没有任何类容。

二、ibdata中system segment header的初始化

本步骤调用 trx_sys_create_sys_pages->trx_sysf_create进行,本步骤除了初始化transaction system segment以外还会初始化其header( ibdata page no 5))信息如下:

/*Createthetrxsysfileblockinanewallocatedfilesegment*/block=fseg_create(TRX_SYS_SPACE,0,TRX_SYS+TRX_SYS_FSEG_HEADER,mtr);//建立segmentbuf_block_dbg_add_level(block,SYNC_TRX_SYS_HEADER);ut_a(block->page.id.page_no()==TRX_SYS_PAGE_NO);page=buf_block_get_frame(block);//获取内存位置mlog_write_ulint(page+FIL_PAGE_TYPE,FIL_PAGE_TYPE_TRX_SYS,//写入block的类型MLOG_2BYTES,mtr);.../*Startcountingtransactionidsfromnumber1up*/mach_write_to_8(sys_header+TRX_SYS_TRX_ID_STORE,1);//初始化TRX_SYS_TRX_ID_STORE/*Resettherollbacksegmentslots.OldversionsofInnoDBdefineTRX_SYS_N_RSEGSas256(TRX_SYS_OLD_N_RSEGS)andexpectthatthewholearrayisinitialized.*/ptr=TRX_SYS_RSEGS+sys_header;len=ut_max(TRX_SYS_OLD_N_RSEGS,TRX_SYS_N_RSEGS)*TRX_SYS_RSEG_SLOT_SIZE;//TRX_SYS_OLD_N_RSEGS为256个memset(ptr,0xff,len);//将slot的信息的全部初始化为ffptr+=len;ut_a(ptr<=page+(UNIV_PAGE_SIZE-FIL_PAGE_DATA_END));/*Initializeallofthepage.Thispartusedtobeuninitialized.*/memset(ptr,0,UNIV_PAGE_SIZE-FIL_PAGE_DATA_END+page-ptr);//将剩下的空间设置为0x00mlog_log_string(sys_header,UNIV_PAGE_SIZE-FIL_PAGE_DATA_END+page-sys_header,mtr);/*CreatethefirstrollbacksegmentintheSYSTEMtablespace*/slot_no=trx_sysf_rseg_find_free(mtr,false,0);page_no=trx_rseg_header_create(TRX_SYS_SPACE,univ_page_size,ULINT_MAX,slot_no,mtr);//将第一个slot固定在ibdata中

完成了这一步过后ibdata的 block 5 就初始化完了,而且我们看到所有的rollback segment slots 都初始化完成(源码所示有256个,实际上最多只会有128个,其中0号solt固定在ibdata中),注意这里的槽大小是TRX_SYS_RSEG_SLOT_SIZE设置的大小为8字节,4字节space id ,4字节 page no,它们会指向 rollback segment header所在的位置。

下面是system segment header的定位:

/**Transactionsystemheader*//*-------------------------------------------------------------@{*/#defineTRX_SYS_TRX_ID_STORE0/*!<themaximumtrxidortrxnumbermoduloTRX_SYS_TRX_ID_UPDATE_MARGINwrittentoafilepagebyanytransaction;theassignmentoftransactionidscontinuesfromthisnumberroundedupbyTRX_SYS_TRX_ID_UPDATE_MARGINplusTRX_SYS_TRX_ID_UPDATE_MARGINwhenthedatabaseisstarted*///最大的事物ID,下次实例启动会加上TRX_SYS_TRX_ID_UPDATE_MARGIN启动#defineTRX_SYS_FSEG_HEADER8/*!<segmentheaderforthetablespacesegmentthetrxsystemiscreatedinto*/#defineTRX_SYS_RSEGS(8+FSEG_HEADER_SIZE)/*!<thestartofthearrayofrollbacksegmentspecificationslots*///指向rollbacksegmentheader的槽/*-------------------------------------------------------------@}*/三、进行rollback segment header的初始化

调用 trx_sys_create_rsegs进行:

说明一下关于innodb_undo_logs参数和innodb_rollback_segments参数,他们作用就是设置rollback segment 的个数,本文以128为例。

根据注释和代码innodb_undo_logs已经是个淘汰的参数,应该用innodb_rollback_segments代替。
这两个参数默认是就是TRX_SYS_N_RSEGS及 128 其实不用设置的。本文也用128进行讨论。

参数 innodb_rollback_segments

staticMYSQL_SYSVAR_ULONG(rollback_segments,srv_rollback_segments,PLUGIN_VAR_OPCMDARG,"Numberofrollbacksegmentstouseforstoringundologs.",NULL,NULL,TRX_SYS_N_RSEGS,/*Defaultsetting*/1,/*Minimumvalue*/TRX_SYS_N_RSEGS,0);/*Maximumvalue*/

参数 innodb_undo_logs

staticMYSQL_SYSVAR_ULONG(undo_logs,srv_undo_logs,PLUGIN_VAR_OPCMDARG,"Numberofrollbacksegmentstouseforstoringundologs.(deprecated)",NULL,innodb_undo_logs_update,TRX_SYS_N_RSEGS,/*Defaultsetting*/1,/*Minimumvalue*/TRX_SYS_N_RSEGS,0);/*Maximumvalue*/

TRX_SYS_N_RSEGS 就是128

下面是注释和代码

/*Deprecateinnodb_undo_logs.Butstilluseitifitissettonon-defaultandinnodb_rollback_segmentsisdefault.*/if(srv_undo_logs<TRX_SYS_N_RSEGS){ib::warn()<<deprecated_undo_logs;if(srv_rollback_segments==TRX_SYS_N_RSEGS){srv_rollback_segments=srv_undo_logs;}}初始化rollback segments 段

n_noredo_created=trx_sys_create_noredo_rsegs(n_tmp_rsegs);//创建32个临时rollbacksegments

我们这里不准备考虑临时rollback segments

建立 95个(33-128) 普通rollback segments

ulintnew_rsegs=n_rsegs-n_used;//eg:128-33=95for(i=0;i<new_rsegs;++i){//对每个rollbacksegment进行初始化ulintspace_id;space_id=(n_spaces==0)?0:(srv_undo_space_id_start+i%n_spaces);//获取undospace_id采用取模的方式循环初始化1234ut_ad(n_spaces==0||srv_is_undo_tablespace(space_id));if(trx_rseg_create(space_id,0)!=NULL)

我们能够注意到这里是i % n_spaces的取模方式n_spaces为我们innodb_undo_tablespaces参数设置的值,因此每个rollback segment 是轮序的方式分布到4个不同的undo tablespace中的。

具体的rollback segment header初始化过程

如上是trx_rseg_create调用trx_rseg_header_create完成的。步骤大概如下:

1、建立rollback segment

block=fseg_create(space,0,TRX_RSEG+TRX_RSEG_FSEG_HEADER,mtr);//建立一个回滚段,返回段头所在的块

2、初始化TRX_RSEG_MAX_SIZE和TRX_RSEG_HISTORY_SIZE信息

/*Initializemaxsizefield*/mlog_write_ulint(rsegf+TRX_RSEG_MAX_SIZE,max_size,MLOG_4BYTES,mtr);/*Initializethehistorylist*/mlog_write_ulint(rsegf+TRX_RSEG_HISTORY_SIZE,0,MLOG_4BYTES,mtr);flst_init(rsegf+TRX_RSEG_HISTORY,mtr);

3、初始化每个undo segment header所在的page no

for(i=0;i<TRX_RSEG_N_SLOTS;i++){//TRX_RSEG_N_SLOTS为1024初始化每个槽值为4字节指向undosegmentheader的pagenotrx_rsegf_set_nth_undo(rsegf,i,FIL_NULL,mtr);}

初始化的情况下我们看到指向的page no都是 FIL_NULL,说明没有分配任何实际的undo segment。

4、整个rollback segment 初始化完成后将space id和page no 写回到 transaction system segment header中。

sys_header=trx_sysf_get(mtr);//获取5号block指针跳过FIL_PAGE_DATA38Utrx_sysf_rseg_set_space(sys_header,rseg_slot_no,space,mtr);//设置spacetrx_sysf_rseg_set_page_no(sys_header,rseg_slot_no,page_no,mtr);//设置no下面是 rollback segment header的结构

/*Transactionrollbacksegmentheader*//*-------------------------------------------------------------*/#defineTRX_RSEG_MAX_SIZE0/*Maximumallowedsizeforrollbacksegmentinpages*/#defineTRX_RSEG_HISTORY_SIZE4/*Numberoffilepagesoccupiedbythelogsinthehistorylist*///history链表大小#defineTRX_RSEG_HISTORY8/*Theupdateundologsforcommittedtransactions*///链表头basenode他们通常调用include/fut0lst.ic中的函数进行更改#defineTRX_RSEG_FSEG_HEADER(8+FLST_BASE_NODE_SIZE)/*Headerforthefilesegmentwherethispageisplaced*/#defineTRX_RSEG_UNDO_SLOTS(8+FLST_BASE_NODE_SIZE+FSEG_HEADER_SIZE)/*Undologsegmentslots*////*-------------------------------------------------------------*/

作为 base node的 TRX_RSEG_HISTORY我们可以看到定义如下

/*Wedefinethefieldoffsetsofabasenodeforthelist*/#defineFLST_LEN0/*32-bitlistlengthfield*/#defineFLST_FIRST4/*6-byteaddressofthefirstelementofthelist;undefinedifemptylist*/#defineFLST_LAST(4+FIL_ADDR_SIZE)/*6-byteaddressofthelastelementofthelist;undefinedifemptylist*/#defineFIL_ADDR_PAGE0/*firstinaddressisthepageoffset*/#defineFIL_ADDR_BYTE4/*thencomes2-bytebyteoffsetwithinpage*/#endif/*!UNIV_INNOCHECKSUM*/#defineFIL_ADDR_SIZE6/*addresssizeis6bytes*/

多了一个长度

到这里128 rollback segment已经初始化完成,并且 每个都包含1024个 undo segment slots。

四、整个过程初始化完成后的分布图

为了让图更加美观和好理解,我这里使用的是innodb_undo_tablespaces=2的情况下作图,也就是只有2个 undo tablespace的情况。其实4个也是同样的道理,因为rollback segment slot是轮询在表空间分配的。

undo phy5.jpg

最终我们看到初始化完成后undo segment slot指向的都是FIL_NULL,及没有指向,当实际分配的时候这些slot就会指向我们的undo segment header。

同时我们可以看看undotablespace到底包含哪些类型块,使用自制的小工具读取如下:

./myblockundo001-d|morecurrentreadblocksis:0--ThisBlockisfilespaceheaderblocks!currentreadblocksis:1--ThisBlockisinsertbufferbitmapblocks!currentreadblocksis:2--ThisBlockisinodeblocks!currentreadblocksis:3--ThisBlockissystemblocks!currentreadblocksis:4--ThisBlockissystemblocks!currentreadblocksis:5--ThisBlockissystemblocks!currentreadblocksis:6--ThisBlockissystemblocks!currentreadblocksis:7--ThisBlockissystemblocks!currentreadblocksis:8--ThisBlockissystemblocks!currentreadblocksis:9--ThisBlockissystemblocks!currentreadblocksis:10--ThisBlockissystemblocks!currentreadblocksis:11--ThisBlockissystemblocks!currentreadblocksis:12--ThisBlockissystemblocks!currentreadblocksis:13--ThisBlockissystemblocks!currentreadblocksis:14--ThisBlockissystemblocks!currentreadblocksis:15--ThisBlockissystemblocks!currentreadblocksis:16--ThisBlockissystemblocks!currentreadblocksis:17--ThisBlockissystemblocks!currentreadblocksis:18--ThisBlockissystemblocks!currentreadblocksis:19--ThisBlockissystemblocks!currentreadblocksis:20--ThisBlockissystemblocks!currentreadblocksis:21--ThisBlockissystemblocks!currentreadblocksis:22--ThisBlockissystemblocks!currentreadblocksis:23--ThisBlockissystemblocks!currentreadblocksis:24--ThisBlockissystemblocks!currentreadblocksis:25--ThisBlockissystemblocks!currentreadblocksis:26--ThisBlockissystemblocks!currentreadblocksis:27--ThisBlockisundoblocks!currentreadblocksis:28--ThisBlockisundoblocks!currentreadblocksis:29--ThisBlockisundoblocks!currentreadblocksis:30--ThisBlockisundoblocks!currentreadblocksis:31--ThisBlockisundoblocks!currentreadblocksis:32--ThisBlockisundoblocks!currentreadblocksis:33--ThisBlockisundoblocks!currentreadblocksis:34--ThisBlockisundoblocks!currentreadblocksis:35--ThisBlockisundoblocks!currentreadblocksis:36--ThisBlockisundoblocks!currentreadblocksis:37--ThisBlockisundoblocks!currentreadblocksis:38--ThisBlockisnewallocateblocks!currentreadblocksis:39--ThisBlockisnewallocateblocks!currentreadblocksis:40--ThisBlockisnewallocateblocks!currentreadblocksis:41--ThisBlockisnewallocateblocks!currentreadblocksis:42--ThisBlockisnewallocateblocks!

这里 block3-block26 就是我们的rollback segment header block。我这里当然是 4个undo tablespace的情况,看的是undo tablespace 1。看来没有问题。分析正确。

五、总结

普通的undo segment的关联方式是:ibdata的block 5 system segment header通过33-128这些 rollback segment slot 轮询指向不同的undo tablespace 的rollback segment header,然后每个rollback segment header中有1024个slot来指向实际的undo segment header,来实现的。实际的undo block会挂载到undo segment header下的链表中。

undo tablespaces数量的变化只能通过重新初始化实例来改变,space id是固定了,所以要考虑清楚

innodb_undo_tablespaces是undo tablespace的数量而innodb_rollback_segments是 rollback segment的数量,参数innodb_undo_logs已经过时了,它和innodb_rollback_segments是同样的功能,默认他们都是128

rollback segment slot 0 固定在 ibdata中,而 rollback segment slot 1-32 为临时rollback segment,33-128才是普通事物的rollback segment。

参考文献:
http://mysql.taobao.org/monthly/2015/04/01/阿里内核月报

作者微信:gp_22389860