天天看点

innodb purge操作

Purge搜集的几个感觉不错的文章

一、

​​ http://blog.sina.com.cn/s/blog_aed82f6f010195me.html​​

DML操作都会生成Undo页,系统需要定期对这些undo页进行清理,也就是所谓purge操作。在5.5之前这些都是在master线程中完成,但5.5及之后的版本可以通过innodb_purge_threads来控制是否使用独立线程进行purge操作。

undolog以及purge线程如何运作做介绍

1.undo log

Undolog保存了记录修改前的镜像。其中insertundo records在事务回滚时会被丢弃,而updateundo records会被用到rollback,MVCC以及Purge操作中。

delete操作:DELETEFROM t WHERE c = 1,innodb不会立刻移除相应的记录,在innodb的官方博客上是这么解释的:

  1. It marks the record as deleted by setting a bit in the control bits of the record.
  2. Stores the before image of the modified columns to the UNDO log
  3. Updates the system columnsDB_TRX_ID and DB_ROLL_PTR in the clustered index record.DB_TRX_ID identifies the transaction that made the last change, and DB_ROLL_PTR points to the new UNDO log record. This UNDO log record contains the old values of DB_TRX_ID and DB_ROLL_PTR,possibly pointing to an older transaction and undo log entry.

在每个记录上有两个重要的系统数据列,用于多版本的控制:

DB_TRX_ID:最近修改这个记录的事务ID

DB_ROLL_PTR:指向由于最近的更新创建的回滚段

undorecord上也会指向旧的undo记录,从而形成了一条更新链,通过这个更新链,不同的事务可以找到其对应的版本的undo信息,组合成就版本记录。

undolog和标记删除的记录可能被其他事务所使用。当没有事务需要这些数据时,标记删除的记录和相关的undolog records可以被purge掉。

2.purge

Purge操作会克隆最老旧的read_view,这个read_view中持有控制信息以限制哪些由其他事务引起的变化是可见的。然后以相反的方式读取从最老的到最近的undolog记录;如果当前运行的事务都没有引用这些记录,则分析这些记录并从索引上移除被标记删除的记录。

有两个相关的控制参数:

Innodb_purge_threads:控制是否使用独立purge线程

Innodb_purge_batch_size:表示一次完成多少个undolog page;但这个值有一个有趣的副作用是会影响到undolog的释放,因为总是在128轮purge后释放undolog page,在5.5及之后版本,开始支持128个回滚段。

一个大致的堆栈如下:

#0 row_purge (thr=0xabd4668, node=0xabd46d0) at/home/yinfeng/Percona-Server-5.5.18-rel23.0/storage/innobase/row/row0purge.c:757

#1 row_purge_step (thr=0xabd4668) at/home/yinfeng/Percona-Server-5.5.18-rel23.0/storage/innobase/row/row0purge.c:805

#2 0x0856bb0e in que_thr_step (thr=0xabd4668) at/home/yinfeng/Percona-Server-5.5.18-rel23.0/storage/innobase/que/que0que.c:1259

#3 que_run_threads_low (thr=0xabd4668) at/home/yinfeng/Percona-Server-5.5.18-rel23.0/storage/innobase/que/que0que.c:1319

#4 que_run_threads (thr=0xabd4668) at/home/yinfeng/Percona-Server-5.5.18-rel23.0/storage/innobase/que/que0que.c:1356

#5 0x08492922 in trx_purge (limit=20) at/home/yinfeng/Percona-Server-5.5.18-rel23.0/storage/innobase/trx/trx0purge.c:1194

#6 0x0848766d in srv_purge_thread (arg=0x0) at/home/yinfeng/Percona-Server-5.5.18-rel23.0/storage/innobase/srv/srv0srv.c:3897

#7 0xb76d3d31 in start_thread (arg=0x8d907b70) at pthread_create.c:304

#8 0xb75ed0ce in clone () at../sysdeps/unix/sysv/linux/i386/clone.S:130

Purge操作的入口函数为srv_purge_thread,主要工作在一个大while循环里。主要处理函数

trx_purge。

Trx_purge的参数也就是Innodb_purge_batch_size

主要流程如下:

1).rw_lock_x_lock(&purge_sys->latch);

2)判断DML操作是否需要被delay,只有当参数innodb_max_purge_lag被设置为一个大于0的值时,才会去判断。

3)获取当前最老旧的事务read_view(read_view_oldest_copy_or_open_new)

4) thr =que_fork_start_command(purge_sys->query); //不是很明白,留待后续研究

5)主函数调用流程:

que_run_threads->que_run_threads_low-> que_thr_step-> row_purge_step->row_purge

que开头的函数都定义在que0que.cc文件中,看起来是根据查询计划图来调度相应的模块函数。

比如对于purge操作,会在que_thr_step里调用row_purge_step,而对于commit,会在row_purge_step里调用trx_commit_step。这部分内容还不是很了解,后面会跟进分析

row_purge到底做了什么吧

1)trx_purge_fetch_next_rec,从历史undolog列表中选择一条记录

id的回滚段(purge_sys->rseg)

trx_purge_get_rseg_with_min_trx_id

——从回滚段中读取记录

trx_purge_read_undo_rec

purge_sys->n_pages_handled做加1,如果已经purge的记录超过了Innodb_purge_batch_size,则设置purge_sys->state= TRX_STOP_PURGE

2)row_purge_parse_undo_rec

undolog里解析出行引用信息和其他信息,返回值为true表明需要执行purge操作.

trx_undo_rec_get_pars获得undo记录的类型,主要包括以下几个类型:

TRX_UNDO_INSERT_REC:

freshinsert into clustered index

TRX_UNDO_UPD_EXIST_REC:

updateof a non-delete-marked record

TRX_UNDO_UPD_DEL_REC

updateof a delete marked record to a not delete marked record; also the

fieldsof the record can change

TRX_UNDO_DEL_MARK_REC

deletemarking of a record; fields do not change

TRX_UNDO_CMPL_INFO_MULT

compilationinfo is multiplied by this and ORed to the type above

TRX_UNDO_UPD_EXTERN

Thisbit can be ORed to type_cmpl to denote that we updated externalstorage fields: used by purge to free the external storage *

purge操作时,需要:

row_mysql_freeze_data_dictionary(trx);

create/droptable操作,因为会对dict_operation_lock加S锁,这是个全局锁。当create/drop表的时候需要X锁,一些后台操作例如Purge、rollback、外键检查需要S锁,在一个事务里,我们可以通过

trx_struct::dict_operation_lock_mode来查看是否拥有该锁

trx_undo_rec_get_row_ref(从undolog中获得逻辑记录信息??)

trx_undo_update_rec_get_update(基于undolog创建updatevector??)

updateundo log record中创建一个partialrow;It contains the

columnswhich occur as ordering in any index of the table

3)对一个标记删除的记录做purge操作

row_purge_del_mark

4)row_purge_upd_exist_or_extern()

Purgesan update of an existing record. Also purges an update of a deletemarked record if that record contained an externally stored field.

purge,会依次查看二级索引,调用row_build_index_entry创建一个老的记录,然后调用row_purge_remove_sec_if_poss从二级索引种将旧记录删除。

row_purge_remove_clust_if_poss来删除主键上的记录

6)row_mysql_unfreeze_data_dictionary,释放在dict_operation_lock上的S锁

7)清理操作

参考资料:

1.​​http://blogs.innodb.com/wp/2011/07/allow-undo-logs-to-reside-in-their-own-tablespace/​​

2.​​http://blogs.innodb.com/wp/2011/04/mysql-5-6-multi-threaded-purge/​​

3.percona5.5.18

二、

参数innodb_fast_shutdown提醒了我一直没研究过的innodb特性之一——insert buffer。首先简述这个参数,然后谈谈insert buffer的实现。

Innodb_fast_shutdown告诉innodb在它关闭的时候该做什么工作。有三个值可以选择:

1.  0表示在innodb关闭的时候,需要purge all, merge insert buffer,flush dirty pages。这是最慢的一种关闭方式,但是restart的时候也是最快的。后面将介绍purge all,merge insert buffer,flush dirty pages这三者的含义。

2.  1表示在innodb关闭的时候,它不需要purge all,merge insert buffer,只需要flush dirty page。

3.  2表示在innodb关闭的时候,它不需要purge all,merge insert buffer,也不进行flush dirty page,只将log buffer里面的日志flush到log files。因此等下进行恢复的时候它是最耗时的。

那么在mysql restart的时候它的恢复流程(也称作crash recovery)是怎么样的呢?

1.   如果在上次关闭innodb的时候是在innodb_fast_shutdown=2或是mysql crash这种情况,那么它会利用redo log重做那些已经提交了的事务。

2.   接下来的操作就是这么几个:

a>     Rollback uncompleted transitions 取消那些没有提交的事务

b>     Purge all 清除无用的undo页

c>      Merge insert buffer 合并插入缓冲

下面详解purge all、merge insert buffer、flush dirty page

1.   Purge all

 这个操作主要是删除那些无用的undo页。对于delete操作,innodb是通过先将要删除的那一行标记为删除,而不是马上清除这一行,因为innodb实现了MVCC,这些undo段用来实现MVCC机制。MVCC也就是常说的多版本控制,锁不阻塞读,读也不阻塞写,这样大大提高了并发性。那么在一致性读的时候,怎么才能找到和事务开始的那个版本呢?对于主键索引,每个行都有一个事务ID和一个undo ID,这个undo ID指向了这行的先前版本的位置。对于非主键索引,也就是常说的secondary index,是通过先找主键索引再找到undo段。而对于update操作,则是先标记删除,然后insert一个新的行,接下来如果有一致性读,那么查找old version的行的原理和delete操作是一样的,详情见[1]。现在接着说purge all操作,随着DML的操作越来越多,那么回滚段必然也会越来越多导致占用了许多磁盘空间,那么innodb就会定期删除一些无用的undo页,首先,innodb重启的时候必然undo页都会无效所以会进行purge all操作,另外,随着时间的推移必然一些事务已经完成,它们已不再需要某些undo页,那么这些undo在mysqld running的时候也会定期的进行清除,主要是在master thread中进行,虽然mysql5.5里面增加了一个参数innodb_purge_threads来进行purge工作,但是这个参数的默认值是0,手册上解释说这个功能在mysql5.5中还不完善,增加它的目的只是表明这是innodb的发展方向。

2.   Merge insert buffer

 Insert buffer是innodb的一个特性之一,在非聚簇、且不是唯一索引(即非主键索引、非唯一索引)的情况下,如果插入的索引行所属的页在buffer pool中就直接更新这个页,否则它会将这个索引行插入到insert buffer中,然后定期对这个insert buffer进行合并(合并的本质工作就是将insert buffer中的信息更新到真正的索引文件中去)。因为innodb的secondary index是非聚簇的,那么插入很有可能带来大量的随机I/O,而如果利用insert buffer对一些属于相同页的行进行合并,那么就会减少随机IO从而提高性能。但是这里需要注意的是,insert buffer和doublewrite buffer是类似的概念,他实际上属于system tablespace中的一部分[2],正由于它也是持久化存储,那么在服务器宕机或是重启之后这些信息不会丢失,所以也就有了在前面介绍innodb_fast_shutdown时所说:在innodb重启时,可能需要进行merge insert buffer。那么在什么情况下需要对insert buffer进行merge操作呢?

a>     在innodb restart的时候

b>     master thread会定期的进行merge操作

c>       每次读取secondary index page时,如果所需页不在buffer pool,而这些页在insert buffer中的时候,这时需要先对insert buffer进行合并,然后才能被读取。为什么这样呢?因为所有插入的索引行所属的页如果不在buffer pool中,而又在insert buffer中,那么它一定代表了页的最新状态(不理解?因为每次插入索引行的时候,如果所需页不在

buffer pool中就直接插入到insert buffer中,而一旦insert buffer merge后相关的行也就不在insert buffer更新secondary index page了)。这时或许你会问那么为什么不直接读取insert buffer中的页然后继续操作而一定要合并(更新到索引文件)呢?因为在innodb中是数据文件(也就是主键索引)和索引文件缓存的,在insert buffer中读取了需要的页后,那么必然就会在buffer pool中缓存了这个页,而如果这个页还留在insert buffer中却不更新到secondary index page去,那么,第一,这将不能保证索引文件得到更新;第二,insert buffer的空间会被占用。而如果这一步将insert buffer 合并后,不但减小了insert buffer的使用空间,而且将这merge操作完成了一部分,减小了以后merge的负担(不是有句话叫做今日事今日毕么),不过这也减慢了读的操作,因为读操作必须等待这个页的合并。

3.   Flush dirty page

这是最好理解的一个概念了,刷新脏页到磁盘。Innodb是数据文件和索引文件缓存的(innodb中的数据文件本质上也是索引文件,只是习惯这么称呼而已),从磁盘读到buffer中的文件被修改后,那么就成了dirty page脏页。而如果这些修改页的操作被提交了之后这些页就必须被flush到磁盘上。

啰嗦了这么久基本上将mysql的insert buffer工作原理大致说清楚了,不过需要注意的是在mysql5.5中这个insert buffer已经改名了,叫做change buffer,不见包含了insert buffer,而且包括了update buffer,delete buffer。最后提一句,随着SSD、Fusion IO这类型存储出现,很多时候我们考虑随机IO带来的影响或许对它们就不适用了。

因为没有读源码,这些理解是通过读其他的资料而来的,所以还留下了几个问题:

1.   实现insert buffer的数据结构是什么?我想应该是树状结构,因为这会为合并那一步提升效率。理由:第一,如果是无序链表的最开始的插入效率可能会比较高,但是最终判断哪些行在相同页或是相邻页的时候需要排序,这里的代价会比较高。而有序的链表在性能上没有二叉树这种结构效率高。

2.   Insert buffer占多大空间?如果很小那岂不是只能容纳几行?那么在系统压力的时候,有空间来应付插入压力么?而如果比较大的,那么怎么保证在合并时候的效率?

参考文档:

[1] http://blogs.innodb.com/wp/2010/09/mysql-5-5-innodb-change-buffering/

[2] http://www.mysqlperformanceblog.com/2009/01/13/some-little-known-facts-about-innodb-insert-buffer/

继续阅读