持久性与原子性
在写完之前文章,通过了解到的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+树中还可能牵扯到页的分裂和合并,
多索引下日志执行的问题