赞
踩
大致工作原理
- 先抛开undo log的各种实现细节,来说说undo log的大致工作原理:
- 工作原理如下:
- 对于每个事务来说,其都有一个对应的undo log
- 当我们开启事务,对表操作之后,其操作的记录都会记录到undo log中
- 当事务正确无误执行完之后,undo log什么都不做,原表被修改
- 当事务执行错误,或者需要回滚时,那么就根据undo log中记录的信息将原表恢复到原来的状态
undo段
- redo存放在重做日志文件中,与redo不同,undo存放在数据库内部的一个特殊段(segment)中,这个段称为undo段
- undo段位于共享表空间内
- 可以通过py_innodb_page_info.py工具来查看当前共享表空间中undo的数量。下面的代码显示了当前的共享表空间ibdata1内有2222个undo页
对undo的一个误解
- 用户通常对undo有这样的误解:undo用于将数据库物理地恢复到执行语句或事务之前的样子——但事实并非如此
- undo是逻辑日志,因此只是将数据库逻辑地恢复到原来的样子。所有修改都被逻辑地取消了,但是数据结构和页本身在回滚之后可能大不相同。这是因为在多用户并发系统中,可能会有数十、数百甚至数千个并发事务。数据库的主要任务就是协调对数据记录的并发访问。比如,一个事务在修改当前一个页中某几条记录,同时还有别的事务在对同一个页中另几条记录进行修改。因此,不能将一个页回滚到事务开始的样子,因为这样会影响其他事务正在进行的工作
- 例如,用户执行了一个INSERT 10W条记录的事务,这个事务会导致分配一个新的段,即表空间会增大。在用户执行 ROLLBACK时,会将插入的事务进行回滚,但是表空间的大小并不会因此而收缩
- 因此,当 InnoDB存储引擎回滚时,它实际上做的是与先前相反的工作:
- 对于每个INSERT,InnoDB存储引擎会完成一个DELETE
- 对于每个DELETE,InnoDB存储引擎会执行一个INSERT
- 每个UPDATE,InnoDB存储引擎会执行一个相反的UPDATE,将修改前的行放回去
innodb_undo_directory参数
- 该参数用于设置rollback segment文件所在的路径
- 这意味着rollback segment可以存放在共享表空间以外的地方,即可以设置为独立表空间
- 该参数默认值为“.”,表示当前InnoDB存储引擎的目录
innodb_undo_logs参数
- 该参数用来设置rollback segment的个数,默认值为128
- 在InnoDB 1.2版本中,该参数用来替换之前版本的参数innodb_rollback_segements
innodb_undo_tablespaces参数
- 该参数用来设置构成rollback segment文件的数量,这样rollback segment可以较为平均地分布在多个文件中
- 设置该参数后,会在路径innodb_undo_directory看到undo为前缀的文件,该文件就代表rollback segment文件
一些注意事项(重要)
- 需要注意的是,事务在undo log segment分配页并写入undo log的这个过程同样需要写入重做日志
- 当事务提交时,InnoDB会做以下两件事情:
- 将undo log放入列表中,以供之后的purge操作
- 判断undo log所在的页是否可以重用,若可以分配给下个事务使用
- 事务提交后并不能马上删除undo log以及undo log所在的页。这是因为可能还有其他事务需要通过undo log来得到行记录之前的版本。因此事务提交时将undo log放入一个链表中,是否可以最终删除undo log以及undo log所在页由purge线程来判断
undo页的重用
- 此外,若为每一个事务分配一个单独的undo页回非常浪费存储空间,特别是对于OLTP的应用类型。因为在事务提交时,可能并不能马上释放页
- 假设某应用的删除和更新操作的TPS(transaction per second)为1000,为每个事务分配一个undo页,那么一分钟就需要1000*60个页,大约需要的存储空间为1GB。若每秒的purge页的数量为20,这样的设计对磁盘空间有着相当高的要求
- 因此,在InnoDB存储引擎的设计中对undo页可以进行重用。具体来说,当事务提交时,首先将undo log放入链表中,然后判断undo页的使用空间是否小于3/4,若是则表示该undo页可以被重用,之后新的undo log记录在当前 undo log的后面
- 由于存放 undo log的列表是以记录进行组织的,而undo页可能存放着不同事务的 undo log,因此purge操作需要涉及磁盘的离散读取操作,是一个比较缓慢的过程
insert undo log
- insert undo log是指在insert操作中产生的undo log
- 因为insert操作的记录只对事务本身可见,对其他事务不可见(这是事务隔离性的要求),故该undo log可以在事务提交后直接删除,不需要进行purge操作
- 下图显示了insert undo log的格式:
- *:表示该字段处进行了压缩
- next:2字节,记录了下一个undo log的位置,通过next的字节可以知道一个undo log所占的空间字节数
- type_cmpl:1字节,记录的是undo的类型,对于insert undo log来说,这个值是11
- undo_no:记录事务的ID
- table_id:记录undo log所对应的表对象
- 下面的部分记录了所有主键的列和值。在进行rollback时根据这些值可以定位到具体的记录,然后进行删除记录
- start:2字节,记录的是undo log的开始位置
update undo log
- update undo log记录的是对delete或update操作产生的undo log
- 该undo log可能需要提供MVCC机制,因此不能在事务提交时就进行删除。提交时放入undo log链表,等待purge线程进行最后的删除
- update undo log的结构如下所示:
- *:表示该字段处进行了压缩
- next、start、undo_no、table_id、n_unique_index与上面介绍的insert undo log一样
- type_cmpl:由于update undo log本身还有分类,因此其可能的取值如下:
- 12(TRX_UNDO_UPD_EXIST_REC):更新non-delete-mark的记录
- 13(TRX_UNDO_UPD_DEL_REC):将delete的记录标记为not delete
- 14(TRX_UNDO_DEL_MARK_REC):将记录标记为delete
- update_vector:表示update导致发生改变的列。每个修改的列信息都要记录在undo log中。对于不同的undo log类型,可能还需要记录对索引列所做的修改
innodb_trx_rollback_segment表
- 该表用来查看rollback segment,其表结构如下所示:
desc information_schema.innodb_trx_rollback_segment;
- 例如通过下面的命令来查看rollback segment的信息
select segment_id,space,page_no from innodb_trx_rollback_segment;
innodb_trx_undo表
- 该表记录事务对应的undo log,方便开发人员详细了解每个事务产生的undo量
insert的演示案例
- 创建一张表t:
create table t( a int, b varchar(32), primary key(a), key(b) )engine=innodb;
- 接着开始一个事务并插入一条数据,然后查看innodb_trx_undo表中该事务的undo log的情况:
- 事务ID为3001
- rollback segment的ID为2
- undo_rec_no为0:这条语句时该事务的第一个操作
- undo_rec_type:为TRX_UNDO_INSERT_REC表示插入的类型,表示的是insert undo log
- size:表示undo log的大小,占用12字节
- 最后space、page_no、offset表示undo log开始位置
begine; insert into t select 1,'1'; select * from information_schema.innodb_trx_undo\G
- 打开ibdata1文件,定位到页(334,272),并读取12字节,内容如下:
- 上面就是undo log实际的内容,根据上面对undo log格式的介绍,整理得:
- 由于rollback segment的ID为2,用户可以通过innodb_trx_rollback_segment表来查看当前rollback segment的信息,例如:
- insert_undo_list为1
- insert_undo_cacheed为0
select segment_id,insert_undo_list,insert_undo_cached from information_schema.innodb_trx_rollback_segment where segment_id=2\G
- 此时提交事务,然后再查看 innodb_trx_rollback_segment表格:
- insert_undo_list变为0,insert_undo_cacheed变为1。这就是前面介绍的undo页的重用
commit; select segment_id,insert_undo_list,insert_undo_cached from information_schema.innodb_trx_rollback_segment where segment_id=2\G
- 当下次再有事务需要向该rollback segment申请undo页时,可以直接使用该页
delete的演示案例
- 接着观察delete操作产生的undo log
begin; delete from t where a=1; select * from information_schema.innodb_trx_undo\G
- 用同样的方法定位到页326,偏移量为620的位置,结果如下:
- 然后进行整理:
- 观察rollback segment的信息,可以看到:
select segment_id,insert_undo_list,insert_undo_cached from information_schema.innodb_trx_rollback_segment where segment_id=2\G
- 同样的,在事务提交后,undo页会放入cache列表以供下次重用
commit; select segment_id,insert_undo_list,insert_undo_cached from information_schema.innodb_trx_rollback_segment where segment_id=2\G
- 从上面可以看到,delete操作并不直接删除记录,而只是将记录标记为已删除,也就是讲记录的delete flag标记为1.而记录最终的删除是在purge操作中完成的
update的演示案例
- 首先再次插入记录(1,'1'),然后进行update操作,同时通过数据字典表innodb_trx_undo观察undo log的情况:
insert into t select 1,'1'; begin; update t set b='2' where a=1; select * from information_schema.innodb_trx_undo\G
- 用同样的方法定位到页318,偏移量为724的位置,得到的结果如下:
- 整理可得:
- 上面的例子是更新一个非主键值,若更新的对象时一个主键值,那么其产生的undo log完全不同,例如:
rollback; update t set a=2 where a=1; select * from information_schema.innodb_trx_undo order by undo_rec_no\G
- 可以看到,update主键的操作分为两步完成,首先将原主键标记为已删除,因此需要产生一个类型为TRX_UNDO_DEL_MARK_REC的undo log,之后插入一条新的记录,因此需要产生一个类型为TRX_UNDO_INSERT_REC的undo log。undo_rec_no显示了产生日志的步骤
- 对undo log不再详细进行分析,相关内容和之前介绍的一样
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。