4.5 撕裂的页面
目录
4.5 撕裂的页面
4.5.1 双写缓冲区的作用
4.5.2 双写缓冲区的结构
4.5.3 双写缓冲区与Redolog的协同工作流程
4.5.2 双写缓冲区写入时机
4.5.3 禁用双写缓冲区
4.5.4 小结
未完待续...
上文我们学习了redo log的结构和其工作原理,它是一个记录物理逻辑的日志,描述了数据库记录的逻辑变化,用于崩溃恢复。但是读者是否进一步想过:从数据库层到硬件层还有操作系统层,每一层操作数据读写的最小颗粒都存在差异,所以就会导致一个问题,当数据库正在将一个数据页从缓冲池(buffer pool)写入磁盘时,如果在这个过程中发生崩溃(如操作系统崩溃或硬件故障),可能会导致只有这个页的一部分被写入。这就是所谓的部分写入,它会破坏页的完整性,从而导致数据损坏。那么怎么办呢?
4.5.1 双写缓冲区的作用
为了解决这个问题,InnoDB引入了双写缓冲区。用来加强数据完整性和恢复能力。其核心目的是防止在数据库崩溃时出现部分写入,也就是说,当数据库正在写入一个页到磁盘,但因为崩溃导致操作只完成了一部分,这个页可能会损坏。它的出现避免了这种情况的发生。从逻辑上来讲,双写缓冲区可以分为两个主要部分:内存中的双写缓冲区和磁盘上的双写缓冲区。
- 内存中的双写缓冲区: 在内存中,双写缓冲区不是一个单独的结构,而是缓冲池(Buffer Pool)的一部分。当页被修改,它们变成脏页并存储在缓冲池中。在这些页被写回磁盘之前,页的副本会被创建并存储在内存的双写区域。这样做是为了在写入操作期间如果发生崩溃,可以从双写缓冲区中的副本恢复数据。
- 磁盘上的双写缓冲区: 在磁盘上,双写缓冲区是共享表空间(例如ibdata1文件)中的一个特定区域。逻辑上,它被分成两个部分,每一部分足够容纳一定数量的连续InnoDB页的副本。磁盘上的双写缓冲区通常是连续的,以减少磁盘I/O操作的开销。
当InnoDB需要将缓冲池中的脏页刷新到磁盘时,它会执行以下步骤:
- 写入双写缓冲区: InnoDB首先将脏页的副本写入内存中的双写结构,然后再将这些内存中的副本写入磁盘上的双写缓冲区。
- 写入最终位置: 一旦磁盘上的双写缓冲区包含了脏页的副本,InnoDB会将这些页从双写缓冲区写入它们在磁盘上的最终位置,即相应的表空间文件(.ibd文件)。
在发生崩溃并随后的恢复过程中,InnoDB会检查双写缓冲区和表空间文件中页的版本。如果表空间文件中的页因为崩溃而损坏,InnoDB会使用双写缓冲区中的副本来恢复这个页。双写缓冲区的设计确保了即使在崩溃发生时也能够恢复所有已提交修改的数据页,防止了部分提交的发生,维护了数据的完整性。
读到这里读者应该会有一个疑问?
1、为什么在内存中和磁盘中都有双写缓冲区?
主要是为了减少磁盘 I/O 操作
- 直接写入磁盘可能涉及多次随机 I/O,这在性能上是非常昂贵的。通过先将数据写入 doublewrite buffer(在内存中),然后统一刷新到磁盘,可以减少磁盘的随机访问,转而利用顺序写入的高效性。顺序写入通常比随机写入快得多。
- 同时如果一个脏页在短时间内多次更新,它可能会先写入内存中的 doublewrite buffer,然后再统一刷新到磁盘的 doublewrite 区域。这种做法可以合并对同一数据页的多次写入,从而减少磁盘写入次数。
4.5.2 双写缓冲区的结构
InnoDB双写缓冲区通常位于系统表空间文件(如ibdata1)内的一个固定区域。逻辑上,它被分为两个部分,每部分可以存储一定数量的页副本。磁盘上的双写缓冲区通常位于共享表空间文件(例如ibdata1)中。它的大小足以存储两个InnoDB数据块。
下图是一个简化的逻辑表示,并不代表双写缓冲区的物理存储的准确布局,旨在帮助读者更好的理解双写缓冲区的概念。
每个数据块包含128个页。由于InnoDB页的默认大小是16KB,每个部分的大小是128 * 16KB = 2MB,因此,其总大小通常为:
2 (块) * 128 (页/块) * 16KB (页大小) = 4MB
需要注意的是,双写缓冲区的大小是不可配置的。它是InnoDB内部决定的一个固定值,为了在系统崩溃时能够恢复被部分写入的页而设计。虽然它确保了数据的持久性和一致性,但它也增加了额外的磁盘I/O,因此可能会稍微影响数据库的写入性能。
当InnoDB需要将脏页刷新到磁盘时,这些页的副本会先写入双写缓冲区的一个部分,然后再写入相应的表空间文件。如果在写入表空间文件时发生崩溃,InnoDB可以使用双写缓冲区中的副本来恢复这些页。
4.5.3 双写缓冲区与Redolog的协同工作流程
下图描述了一个事务提交 + 刷脏,redolog 和 doublewrite buffer 协同工作的流程,帮助读者理解它们是如何配合工作的。
以下是每个步骤的详细描述:
- 事务开始,修改数据: 用户发起一个事务,进行数据修改操作,这会导致数据页被加载到内存(如果不在内存中)并进行修改,这些修改后的页称为“脏页”。
- 记录重做日志到Redo Log Buffer: InnoDB将事务修改的操作转换为重做日志记录,并将这些记录存放到内存中的重做日志缓冲区(Redo Log Buffer)。这些记录精确地描述了如何重做(redo)或回滚(undo)每个修改。
- 事务提交,刷新Redo Log Buffer到重做日志文件: 当事务提交时,InnoDB确保将Redo Log Buffer中的所有重做日志记录刷新到磁盘上的重做日志文件(通常是两个或更多的文件循环使用)。这个步骤是关键的,因为它确保了即使在系统崩溃之后,所有已提交事务的修改都已经安全地记录到磁盘上。
- 写入内存中的双写缓冲区副本: 在脏页被刷新到磁盘之前,InnoDB会先将它们的副本写入到内存中的双写缓冲区。这是为了预防在将脏页写入磁盘表空间文件的过程中发生部分页写入。
- 将双写缓冲区副本写入磁盘上的双写缓冲区: 然后,InnoDB将内存中双写缓冲区的副本写入到磁盘上的双写缓冲区。这个磁盘上的双写缓冲区位于共享表空间文件(如ibdata1)中的一个固定位置。
- 从双写缓冲区写入表空间文件: 最终,InnoDB会将这些页从双写缓冲区复制到磁盘上的表空间文件中,也就是他们的最终存储位置。这是数据持久化的最后一步。
通过这个流程,双写缓冲区和重做日志共同确保了即使在系统崩溃的情况下,也能够恢复到最后已知的一致状态。重做日志记录了事务的所有变更,这样即使脏页还没来得及写入磁盘,这些变更也不会丢失。双写缓冲区则保护了数据页不会因为部分写入而损坏,从而确保了数据的完整性。这些机制是InnoDB提供的ACID事务属性中的一致性(Consistency)和持久性(Durability)的关键部分。
在这里读者可能会产生一些疑问?
1、Doublewrite buffer 写操作是原子的吗?
写入双写缓冲区的操作本身并不是原子的,但InnoDB采用了一些技术手段来保证如果在双写过程中发生崩溃,数据页仍然可以安全地恢复。如果在 Doublewrite buffer 写入过程中发生崩溃:
重启后InnoDB通过检查数据页的LSN和重做日志来判断是否部分写入发生
- 正常情况:如果双写缓冲区中的数据页副本完好,InnoDB会使用这些副本来恢复那些可能已损坏的数据页。
- 异常情况:如果崩溃发生在 Doublewrite buffer 的写入过程中,检查出写入的页是损坏的,因为它既包含旧的数据,也包含新的数据。那么在恢复过程中,InnoDB会使用页的旧副本,也就是磁盘上的原始页(如果之前已经写入)实际上代表了旧版本的数据。如果在双写缓冲区写入过程中发生部分写入,旧版本的数据页仍然保持不变,因此不会立即损坏。
总的来说,双写缓冲区相当于在未刷脏前同时存在两份磁盘页数据,一新(缓冲区数据)一旧(磁盘原有数据),由此来保证在任何情况下都有一份完整的页数据可以供数据恢复使用。并和redolog一起保证了数据的完整性和一致性。
2、如果redolog写入了,但是双写缓冲区中的页副本还未写入,这时候崩溃了怎么办?
如果重做日志(redo log)已经写入磁盘,但双写缓冲区(doublewrite buffer)中的页副本还未写入磁盘时发生了崩溃,InnoDB恢复机制可以确保数据的一致性和完整性。以下是恢复过程的简要说明:
- 启动恢复过程: 当InnoDB数据库重启后,它会启动一个自动恢复过程,检查重做日志文件中的日志序列号(LSN)来确定崩溃发生时最后已知的一致状态。
- 重放重做日志: InnoDB将遍历重做日志,重放所有在崩溃前已提交的事务。对于那些已经写入到重做日志但尚未刷新到数据页的更改,这些更改会被应用到对应的数据页中。这个步骤确保了所有在崩溃前提交的事务都得到了恢复。
- 检查数据页: 如果在重放过程中,重做日志指示某个数据页已经被修改,但是双写缓冲区和表空间文件中的相应页尚未被更新(因为崩溃发生在双写缓冲区将页副本写入磁盘之前),InnoDB会将重做日志中的修改应用到内存中的页副本上。
- 应用未完成的更改: 由于重做日志已经被写入,即使双写缓冲区中的页副本尚未被安全写入磁盘,InnoDB也能够使用重做日志中的信息来更新任何需要更新的数据页。
在这个过程中,双写缓冲区用于防止在页写入操作过程中发生的部分写入问题,而重做日志确保所有提交的事务都能在崩溃后恢复到最后一致的状态。即便双写缓冲区中的页副本尚未写入磁盘,重做日志仍然能够提供足够的信息来重建数据页,并确保数据的正确性。因此,即使在上述情况下发生崩溃,InnoDB的恢复过程能够保证数据页的完整性和事务的持久性,这是由于重做日志记录了足够的信息来重构数据页的状态。
4.5.2 双写缓冲区写入时机
通过以上介绍可以看出,双写缓冲区的工作通常发生在刷脏过程中,脏页通常会在如下几个阶段刷盘,所以双写缓冲区的写入通常也发生在如下场景中:
- 检查点发生时: 在执行检查点(checkpoint)操作时,InnoDB会将缓冲池中的脏页写入到磁盘。检查点是确保所有在某一时间点之前的修改都已经被写入到磁盘的过程。
- 事务提交: 当事务提交时,所有相关的脏页最终都需要被刷新到磁盘上。不过,并不是在事务提交的那一刻立即写入双写缓冲区,因为提交时脏页可能已经在之前的操作中写入过了。而是在提交过程中,任何因为提交而标记为需要刷新的脏页,在刷新到磁盘前会使用双写缓冲区。
4.5.3 禁用双写缓冲区
在某些特定场景中,如果系统的磁盘子系统能够确保页的原子写入(atomic write),例如使用某些类型的RAID或电池支持的写入缓存,那么双写缓冲区可能不是必须的。在这些情况下,可以通过MySQL配置参数来禁用双写缓冲区,从而减少I/O开销。然而,在大多数情况下,推荐保持双写缓冲区启用,以确保数据的安全。
InnoDB的双写缓冲区(Doublewrite Buffer)可以通过MySQL配置文件(my.cnf 或 my.ini)来开启或关闭。要禁用双写缓冲区,你需要在[mysqld]部分设置innodb_doublewrite为0。以下是如何禁用双写缓冲区的示例:
innodb_doublewrite = 0
若要启用双写缓冲区(通常是默认设置),则需要将innodb_doublewrite设置为1:
innodb_doublewrite = 1
修改配置文件后,为了使更改生效,你需要重新启动MySQL服务。
在禁用双写缓冲区之前,你需要确保你完全了解这样做的风险。双写缓冲区是一种重要的数据完整性保护机制,用于防止在系统崩溃时发生的部分写入错误。禁用它可能会提高性能,但同时也增加了数据损坏的风险。在某些情况下,当你使用的存储设备(如某些类型的SSD)提供自己的原子写入保护机制时,禁用双写缓冲区可能是安全的。然而,在大多数情况下,为了确保数据安全,建议保留双写缓冲区的默认启用状态。
4.5.4 小结
通过本节的学习,读者应该对InnoDB的双写缓冲区有了深入的理解,包括其在维护数据库完整性和提升系统可靠性方面的关键作用,以及它在整个InnoDB架构中的位置和重要性。同时,也应该意识到在考虑性能优化时需要在数据安全和性能之间做出权衡。
双写缓冲区的目的
- 防止数据页损坏:双写缓冲区的主要目的是防止在系统崩溃时由于部分写入操作造成的数据页损坏。
- 数据完整性:双写缓冲区是InnoDB保证数据页在崩溃时不损坏的关键部分,从而维护数据完整性。
- 系统可靠性:通过提供数据页的完整副本,双写缓冲区提高了整个数据库系统的可靠性。
双写缓冲区的位置
- 内存中的双写缓冲区:作为性能优化机制,减少磁盘I/O,提高数据写入效率。
- 磁盘上的双写缓冲区:确保数据页在系统崩溃时能从一个完整的副本中恢复,提供数据持久性。
双写缓冲区的工作流程
- 脏页处理:当页被修改后,它们被标记为脏页并最终需要被刷新到磁盘。
- 写入双写缓冲区:在写入数据页到最终位置之前,首先将其副本写入内存中的双写缓冲区,然后刷新到磁盘上的双写缓冲区。
- 与重做日志协同工作:双写缓冲区与重做日志(Redo Log)一同确保了数据的一致性和完整性。即使在重做日志已经写入而双写缓冲区还未完成写入时发生崩溃,InnoDB的恢复机制也能够保证数据的一致性。
性能与数据安全性的权衡
- 禁用双写缓冲区:在特定条件下,系统管理员可能会考虑禁用双写缓冲区以提高性能,但这会牺牲数据安全性。
- 权衡考量:在考虑性能优化时,需要在系统的数据安全和性能之间进行权衡。
本节阐述了InnoDB双写缓冲区的存在目的和工作机制,通过理解双写缓冲区如何在保证数据完整性和系统可靠性方面发挥作用,读者可以获得关于缓存策略设计的重要知识,进而运用到自己的系统设计中去。