MySQL(InnoDB剖析):40---事务之(事务的实现:undo log(回滚日志))

  • 事务隔离性可以使用前面介绍的锁来实现。原子性、一致性、持久性通过数据库的redo log和undo log来完成:
    • redo log:称为重做日志。用来保证事务的原子性和持久性
    • undo log:用来保证事务的一致性
  • redo和undo的作用都可以视为一种恢复操作:
    • redo恢复提交事务修改的页操作
    • undo回滚行记录到某个特定版本
  • 因此两者记录的内容也不同:
    • redo通常是物理日志,记录的是页的物理修改操作
    • undo是逻辑日志,根据每行记录进行记录

一、undo log概述

  • 重做日志记录了事务的行为,可以很好地通过其对页进行“重做”操作。但是事务有时还需要进行回滚操作,这时就需要undo。因此在对数据库进行修改时,InnoDB存储引擎不但会产生redo,还会产生一定量的undo。这样如果用户执行的事务或语句由于某种原因失败了,又或用户用一条rollback语句请求回滚,就可以利用这些undo信息将数据回滚到修改之前的样子

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,将修改前的行放回去
  • 除了回滚操作,undo的另一个作用是MVCC,即在 InnoDB存储引擎中MVCC的实现是通过undo来完成。当用户读取一行记录时,若该记录已经被其他事务占用,当前事务可以通过undo读取之前的行版本信息,以此实现非锁定读取
  • 最后也是最为重要的一点是,undo log会产生redo log,也就是undo log的产生会伴随着redo log的产生,这是因为undo log也需要持久性的保护

二、undo存储管理

  • InnoDB对undo的管理同样采用段的方式。但是这个段和之前介绍的段有所不同:
    • 首先InnoDB有rollback segment,每个回滚段中记录了1024个undo log segment
    • 而在每个undo log segment段中进行undo页的申请
  • 共享表空间偏移量为5的页(0,5)记录了所有rollback segment header所在的页,这个页的类型为FIL_PAGE_TYPE_SYS
  • rollback segment的发展:
    • InnoDB 1.1版本之前(不包括1.1版本),只有一个rollback segment,因此支持同时在线的事务限制为1024。虽然对绝大多数的引用来说都已经够用,但不管怎么说还是一个瓶颈
    • 1.1版本开始InnoDB支持最大128个rollback segment,故其支持同时在线的事务限制提高到了128*1024
    • 虽然InnoDB 1.1版本支持了128个rollback segment,但是这些rollback segment都存储于共享表空间中。才能够InnoDB 1.2版本开始,可通过参数对rollback segment做进一步的设置。这些参数包括:
      • innodb_undo_directory
      • innodb_undo_logs
      • innodb_undo_tablesacpes

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操作需要涉及磁盘的离散读取操作,是一个比较缓慢的过程
  • 通过下面的命令来查看链表中undo log的数量,如:
    • History list length就代表了undo log的数量这里为325
    • purge操作会减少该值。然而由于undo log所在的页可以被重用,因此即使操作发生,History list length的值也可以不为0

三、undo log格式

  • 在InnoDB中,undo log分为:
    • insert undo log
    • update undo log

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类型,可能还需要记录对索引列所做的修改

四、查看undo的信息(InnoSQL才可以查看)

  • Oracle和Microsoft SQL Server数据库都由内部的数据字典来观察当前undo的信息,InnoDB在这方面做得还不够,只能通过原理和经验来进行判断。InnoSQL对information_schema进行了扩展,添加了两张数据字典表,这样用户可以非常方便和快接的查看undo信息

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量

五、undo log操作的演示案例

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不再详细进行分析,相关内容和之前介绍的一样
  • 总之,InnoSQL数据库提供的关于undo信息的数据字典表可以帮助开发人员更好的了解当前各个事务产生的undo信息
发布了1463 篇原创文章 · 获赞 996 · 访问量 35万+

猜你喜欢

转载自blog.csdn.net/qq_41453285/article/details/104346372