Innodb-AD

187 阅读6分钟

持久性与原子性

在写完之前文章,通过了解到的Shadow Paging(COW机制)机制,对Innodb的持久性和原子性产生了新的思考。

[Redo + Undo]是不是[持久性 + 原子性]的必要条件

先说结论不是,不使用日志就可以实现,参考上文。使用日志的话,Undo Only和Redo Only都可以单独实现[持久性 + 原子性]。

定义

  • Durability of Updates:已经Commit的事务的修改,故障恢复后仍然存在;
  • Failure Atomic:失败事务的所有修改都不可见;

Undo Only

方式一:undo 日志

undo日志存储的是记录的旧值。

满足持久性:就需要在事务Commit的时候把内存中的脏数据写盘。

满足原子性:首先需要确保事务脏数据写盘之前Undo日志已经写盘,然后需要有标志位判断事务是否已经提交,这样才可以对未Commit成功的数据进行回滚。

写盘顺序:Undo LOG -> 脏数据 -> Commit标志位

缺点:事务提交,所有脏数据写盘,性能差。

Redo Only

Redo日志存储的是记录的新值。

满足持久性:需要确保事务Commit的时候Redo日志已经写盘。

满足原子性:需要确保未Commit的数据不写盘,事务Commit的标志位要早于脏数据写盘,Redo LOG具有幂等性。

写盘顺序:Redo LOG -> Commit标志位 -> 脏数据

缺点:事务提交之前,脏记录都保存在内存中,对内存的大小要求太高

Redo + Undo

Redo + Undo的形式满足[持久性 + 原子性]只需要确保日志先于Commit标志位、脏数据写盘即可,对Commit标志位和脏数据写盘顺序无要求。

Redo LOG具有幂等性。

Innodb

数据组织形式

以Innodb为例,Innodb在内存和硬盘中都是以页为最小单位组织数据的,写盘也是以页为最小单位。如果使用Undo Only或者Redo Only的话,需要确保"页"的原子性和持久性,因此在同一"页"上不能有并发的事务。数据基于页,这也决定了Redo与Undo的实现。

另外,Innodb中是以B+树来组织逻辑数据的,考虑到B+树分裂合并的策略,因此也需要确保"B+树操作"的原子性。

单条日志格式

Physical Log

A. 记录完整的Page。

B. 记录Page中被修改的部分(page中的偏移,内容和长度)。

优点: 因为恢复时,完全不依赖原页面上的内容,所以不要求持久化了的数据保持在一个一致的状态。比如在写一个页面到磁盘上时,系统发生故障,页面上的一部数据写入了磁盘,另一部分丢失了,这时仍然可以恢复出正确的数据。

缺点: Log记录的内容很多,占用很大的空间。如B-Tree的分裂操作,要记录约一个完整Page的内容。

Logical Log

  逻辑日志是记录在关系(表)上的一个元组操作,比如

  A. 插入一行记录。

  B. 修改一行记录。

  C. 删除一行记录。

  逻辑日志比起物理的日志,显得简洁的多。而且占用的空间也要小的多,但是逻辑日志有2个缺点:

1.部分执行

     例如:表T有2个索引,在向T插入1条记录时,需要分别向2个B-Tree中插入记录。有可能第一个B-Tree插入成功了,但是第二个B-Tree没有插入成功。那么这个日志就处于既不成功又不失败的中间状态。在恢复或回滚时,需要处理这些特殊情况。

2.操作的一致性问题

     一个插入操作有一个B-Tree的分裂,页A的一半数据移到了B页,A页写入了磁盘,B页没有写入磁盘。如果这时候发生了故障,需要进行恢复,逻辑日志是很难搞定的。

Physical Log + Logical Log

这种日志将物理和逻辑日志相结合,取其利,去其害。从而达到一个相对更好的一个状态。这种日志有2个特点:

  A. 物理到page. 将操作细分到页级别。为每个页上的操作单独记日志。

     比如,一个Insert分别在2个B-Tree的节点上做了插入操作,那么就分别为每一个页的操作记录一条日志。

  B. Page内采用逻辑的日志。比如对一个B-Tree的页内插入一条记录时,物理上来说要修改Page Header的内容(如,页内的记录数要加1),要插入一行数据到某个位置,要修改相邻记录里的链表指针,要修改Slot的属性等。从逻辑上来说,就是在这个页内插入了一行记录。因此Page内的逻辑日志只记录:’这是一个插入操作’和’这行数据的内容‘。

  MySQL数据库InnoDB存储引擎的Redo Log 记录的就是这种物理和逻辑相结合的日志。

  使用页内的逻辑日志,可以减少日志占用的空间。但是它毕竟还是逻辑日志,上面提到的2个问题能够避免吗?

  A. 页面内的部分执行的情况可以认为不存在了。因为整个页面的操作是原子操作,在完成之前是不会写到磁盘上的。

  B. 操作一致性的问题仍然存在。如果在写一个Page到磁盘时发生了故障,可能导致Page Header的记录数被加1了,但是数据没有刷新到磁盘上,总之页面上的数据不一致了。

  好在这个问题被缩小到了一个页面的范围内,因此比较容易解决。InnoDB存储引擎中用Double Write的方法来解决这个问题。 app.yinxiang.com/shard/s1/nl…

RedoLOG

Redo需要重放日志,就会牵扯到如下问题:

  • 重放的顺序
  • 重放从哪里开始,到哪里结束
  • 重放的对象
  • 重放的准确性
  • 重放的幂等性如何保证
  • 在B+树的组织形式下,1个操作可能牵扯到多个页的修改,如何保证原子性
MTR

A. MTR的所有日志被封装在一起,当MTR提交时一起写入redo log buffer.

     这样做有2个好处:

     * 减少并发MTR对redo log buffer 的竞争。

     * 连续的存储在一起,恢复时的处理过程更简单。

  B. InnoDB在redo log的层面,将一个MTR中的所有日志作为Redo log的最小单元。在恢复时,一个MTR

     中的所有日志必须是完整的才能进行恢复。

日志形式

一个事务可能涉及多个页的修改,在B+树中还可能牵扯到页的分裂和合并,

多索引下日志执行的问题