荥阳企业网站建设,wordpress标签数量,iis 调用wordpress,找客网1. 前言
InnoDB有两大日志模块#xff0c;分别是redo log和undo log。为了避免磁盘随机写#xff0c;InnoDB设计了redo log#xff0c;数据写入时只写缓冲页和redo log#xff0c;脏页由后台线程异步刷盘#xff0c;哪怕系统崩溃也能根据redo log恢复数据。但是我们漏了一…1. 前言
InnoDB有两大日志模块分别是redo log和undo log。为了避免磁盘随机写InnoDB设计了redo log数据写入时只写缓冲页和redo log脏页由后台线程异步刷盘哪怕系统崩溃也能根据redo log恢复数据。但是我们漏了一种情况没有考虑如果事务执行到一半系统崩溃了redo log没刷盘还好相当于本次事务的修改全部停留在内存里重启后相当于什么也没做。但是如果redo log已经刷盘了MySQL重启后依然会根据redo log恢复页面相当于本次事务执行到一半的状态不符合原子性。为了保证原子性MySQL必须撤销本次事务的所有修改让本次事务「看起来什么都没做」这就是undo log要负责的事情。
2. 事务回滚
事务回滚的需求是存在的除了上述情况系统崩溃时的执行了一半的事务需要回滚很多时候开发者也经常需要通过命令ROLLBACK手动回滚事务。事务回滚后该事务看起来什么都没做一样它是符合原子性的。
如何实现事务回滚呢想当然肯定要把事务中修改的数据先记下来比如
insert一条记录就把主键记下来回滚时删除该记录即可。delete一条记录时把整条记录记下来回滚时重新插入即可。update一条记录时把对应列修改前的值全部记下来回滚时修改回来即可。select不会修改记录无需处理。
InnoDB其实也就是按照这个思路去设计的每次对记录的修改都会记一条日志把回滚该条记录的必要数据给记录下来这个日志就是undo log。
3. undo log格式
undo log是针对记录的一般每对一条记录进行一次改动都会生成1到2条undo log。一个事务在执行过程中可能会修改很多记录也就会生成若干条undo log每个事务生成的undo log都会有一个唯一编号undo no从0开始依次递增undo no越小代表日志越早生成。
另外undo log只针对聚簇索引只有聚簇索引记录才有trx_id和roll_pointer隐藏列二级索引是不会生成undo log的MySQL在事务回滚时会自动撤销对二级索引的变更。
roll_pointer隐藏列占用7个字节组成如下
属性长度说明is_insert1比特是否是TRX_UNDO_INSERT大类rseg id7比特回滚段id最多128个回滚段Page Number4字节undo log所在页号Offset2字节undo log所在页号的偏移量
和redo log一样InnoDB也设计了很多不同类型的undo log增删改操作对应的undo log类型都不一样。
3.1 insert undo log
插入一条记录对应的回滚操作就是删除该条记录对应的undo log最需要记录的就是tableId和主键信息。InnoDB设计了TRX_UNDO_INSERT_REC类型的undo log来回滚insert操作。
属性说明end of record本条undo log结束下一条开始的位置undo typeundo log类型undo noundo log序号table id表对应的id主键信息len,value列表主键各列的长度以及对应的值start of record上一条undo log结束本条开始的位置
重点关注主键信息假设表的主键是BIGINT类型的id我们插入了一条id10000的记录那么主键信息存储的内容就是8,10000如果主键包含多列需要把每个列的长度和值都记录下来。
3.2 delete undo log
删除一条记录对应的回滚操作就是把这条记录再重新插入回去难道undo log要把一条用户记录完整的给记录下来吗这未免也太浪费空间了其实完全不需要这么做这还得说回InnoDB删除记录的流程。
记录头信息里会有next_record属性把记录按照主键串联成一条单向链表。页内被删除的记录也会根据该属性串联成一条单向链表只不过这条链表的空间是可以被重用的也称作「垃圾链表」。索引页Page Header里有PAGE_FREE属性指向这条垃圾链表的头节点。记录头信息里还有delete_mark属性用来标记记录是否被删除。 当我们要删除一条记录时实际上会有两个阶段
阶段1
将记录的delete_mark标记为1记录undo log写入trx_id和roll_pointer。事务提交前记录一直处于这种中间状态既不是正常记录也不是已删除记录。只有将记录从正常链表中移除加入到垃圾链表里记录才算真正删除其它事务也访问不到了。 为啥不直接删除记录而是停留在中间状态 这条记录还需要为MVCC服务其它事务可能还需要访问。 阶段2
事务提交后会有专门的线程来将记录真正的删除掉这个过程称作「purge」。将记录从正常链表中移除加入到垃圾链表InnoDB采用头插法PAGE_FREE会指向该记录记录占用的空间也可以被重用了。与此同时InnoDB还会修改Page Header里的PAGE_N_RECS、PAGE_GARBAGE、Page Directory等信息。 综上所述事务提交前只会经历阶段1事务提交后也就不存在回滚了。所以针对delete操作只需要把阶段1回滚即可又因为阶段1记录其实并没有真正删除所以undo log其实没必要保存完整记录。InnoDB设计了TRX_UNDO_DEL_MARK_REC类型的undo log。
属性说明end of record本条undo log结束下一条开始的位置undo typeundo log类型undo noundo log序号table id表对应的idinfo bits记录头信息的前4个比特位和record_type值old trx_id旧的事务idold roll_pointer旧的回滚指针主键信息len,value列表主键各列长度和值index_col_info len索引列信息总长度索引各列信息pos,len,value索引各列的位置、长度和值start of record上一条undo log结束本条开始的位置
与insert不同的是delete和update操作对应的undo log会记录下旧的trx_id和roll_pointer这样就可以找到上一次对记录修改时的undo log这些undo log串联起来就是传说中的「版本链」服务于MVCC。根据主键信息定位到具体的记录用户回滚时恢复。索引各列信息主要用于purge阶段。
3.3 update undo log
update操作就比较复杂了根据是否更新主键InnoDB的处理方式也是不同的。 一、不更新主键 在不更新主键的前提下如果更新后记录各列的长度与更新前相同那么就可以「就地更新」也就是直接在原有记录上进行更新同时记录下undo log。 注意是每个列的长度都和更新前相同而非记录总长度和更新前相同。 就地更新的条件还是比较苛刻的如果更新后列的长度发生变化那么InnoDB会采用「先删除旧记录再插入新记录」的方式来做更新这里的“删除”是真的将记录删除并移入垃圾链表而非仅仅打删除标记。 为什么会这么做呢 在索引页里记录与记录之间是紧密无间的存储在一起的中间没有空间如果更新后记录占用的空间变大压根就没法存储只能删掉重新申请空间插入一条。 总之针对这种不更新主键的情况InnoDB设计了TRX_UNDO_UPD_EXIST_REC类型的undo log。
属性说明end of record本条undo log结束下一条开始的位置undo typeundo log类型undo noundo log序号table id表对应的idinfo bits记录头信息的前4个比特位和record_type值old trx_id旧的事务idold roll_pointer旧的回滚指针主键信息len,value列表主键各列长度和值n_updated更新的列的数量pod,ole_len,old_val列表更新列的旧值index_col_info len索引列信息总长度索引各列信息pos,len,value索引各列的位置、长度和值start of record上一条undo log结束本条开始的位置
二、更新主键 针对update操作更新了主键的情况InnoDB分为两个阶段来处理
将旧记录进行delete mark操作服务于MVCC。根据更新后各列的值构建一条新记录并插入。
这两个阶段对应两条undo log也就是上面说的TRX_UNDO_DEL_MARK_REC和TRX_UNDO_INSERT_REC。
4. 对覆盖索引查询的影响
聚簇索引记录会有trx_id和roll_pointer隐藏列通过undo log里的roll_pointer串联形成版本链即一条记录存在多个版本在select时会判断哪些版本对当前事务可见。 但是undo log只针对聚簇索引二级索引没有roll_pointer也不会生成undo log。我们又知道InnoDB有个查询优化叫「覆盖索引查询」即直接扫描二级索引返回结果不再根据主键回表查询可以大大提高数据查询的效率。
这时就存在一个问题覆盖索引查询时无法判断二级索引记录是否对当前事务可见 InnoDB的解决方案是在Page Header里有一个属性叫PAGE_MAX_TRX_ID它代表修改当前页的最大事务id如果PAGE_MAX_TRX_ID小于当前活跃的最小事务id代表修改当前页的事务都提交了可以直接使用覆盖索引查询无需回表。反之就需要回表根据聚簇索引的trx_id和roll_pointer以及对应的undo log来判断哪些二级索引记录是对当前事务可见的。