校园网站建设需求分析,深圳专业网站设计制作,设计师网盘,做网站需要什么部门批准阿丹#xff1a; 在刚开始写本文章的是还不太清楚要如何去细啃下这两个体系#xff0c;在查阅资料的过程中。发现大厂阿里的庖丁解InnoDB系列#xff0c;详细了的写了很多底层知识#xff0c;于是基于这个这两个文章才有了阿丹的这篇文章。
整体认知#xff1a; 在 MySQ…阿丹 在刚开始写本文章的是还不太清楚要如何去细啃下这两个体系在查阅资料的过程中。发现大厂阿里的庖丁解InnoDB系列详细了的写了很多底层知识于是基于这个这两个文章才有了阿丹的这篇文章。
整体认知 在 MySQL 中的撤销日志undo log和重做日志redo log的机制。
撤销日志undo log在 MySQL 中被称为回滚日志rollback log用于回滚事务的修改。在事务执行期间所有的修改操作都会被写入回滚日志中以便在发生回滚操作或者系统崩溃时能够撤销事务的修改。回滚日志记录了数据修改前的旧值以便进行回滚操作。
重做日志redo log在 MySQL 中同样被称为重做日志redo log用于事务的持久性和恢复性。在事务提交时重做日志会将事务所做的修改记录下来以便在系统崩溃时能够通过重演这些修改来恢复数据库的状态。重做日志的记录方式是采用追加日志的方式将数据修改操作追加到日志文件中。
这两个日志回滚日志和重做日志是 MySQL 确保事务的原子性、一致性、持久性和可恢复性的关键机制。它们一起确保了数据库的安全性并允许系统在发生故障时能够恢复到正确的状态。
有点不一样
在 InnoDB 存储引擎中独有的日志是撤销日志undo log。InnoDB 引擎使用撤销日志来实现事务的一致性和原子性。
InnoDB 的撤销日志是作为事务日志transaction log的一部分进行记录的其目的是为了支持事务的回滚操作。每个事务在执行期间所做的修改都会被写入撤销日志以确保在事务回滚时可以撤销这些修改。撤销日志记录了数据修改前的旧值以便回滚操作能够正确地恢复数据。
而重做日志redo log在 InnoDB 中同样存在但不属于 InnoDB 引擎独有。重做日志用于事务的持久性和恢复性确保在系统崩溃后可以通过重演事务的修改来恢复数据库的一致性。
总结来说InnoDB 引擎中独有的是撤销日志undo log而重做日志redo log是作为事务日志的一部分存在于多个存储引擎中。
redo log和undo log的关系 InnoDB中其实是把Undo当做一种数据来维护和使用的也就是说Undo Log日志本身也像其他的数据库数据一样会写自己对应的Redo Log通过Redo Log来保证自己的原子性。因此更合适的称呼应该是Undo Data。
理解 也就说其实undo log在数据库中也是和一张表中的数据一样也是使用redo log来保证数据的一致性的。它也是存储在innodb中的数据页16kb物理存储块的限制管理中的。
mysql中的redo log 在其他的数据库以及在其他的数据库管理系统中redo log的作用是保证数据的原子性与持久性。同时为了保证一致性和与隔离性。innodb推出并维护了undo log。并且undo log是受到redo log来管理的。
为什么需要记录REDO 1、为了取得更好的读写性能innodb会将数据缓存在内存中InnoDB Buffer Pool对磁盘数据的修改也会落后于内存。因为是在缓存中所以进程或者机器崩溃的时候就会导致整个内存的数据丢失。 2、为了保证数据库中的持久性以及一致性innodb中维护redo log。 也就是说在数据落库前先将数据生成的的redo log落库持久化。也就是修改page之前需要先将修改的内容记录到redo中。保证redo log在修改page数据页中落盘。 其中涉及到一个概念为WAL
WALWrite Ahead Log实际上是redo log的一种实现方式它是InnoDB为了确保数据的持久性而设计的一种机制。WAL的基本思想是在将数据修改应用到磁盘之前先将这些修改记录到redo log中。这样即使在数据修改应用到磁盘之后发生机器崩溃也可以通过redo log中的信息恢复数据。因此WAL是InnoDB保证数据一致性和持久性的重要手段之一。 就是在故障发生导致内存数据丢失后Innodb会在重启的时候重放redo将page恢复到崩溃前的状态。
需要什么样的REDO 1、事务正确性一个事务在他的redo全部落盘之后才能返回用户成功。 2、redo的数据量要小 3、因为系统崩溃是无法预知的重启重放redo的时候系统是不知道那些redo对应page数据页已经重新落盘了。所以redo的重放必须要可重入所以redo要保证幂等。 4、为了重入的速度一个redo必须只对应一个page
使用对应技术选型来解决这个问题--使用技术特性来解决难题 数据小--》Logical Logging 幂等以及基于page--》Physical Logging 因此innodb采用的日志被称为 Physiological Logging
解释以page为单位但是在Page内以逻辑的方式记录。
官方例子 MLOG_REC_UPDATE_IN_PLACE类型的REDO中记录了对Page中一个Record的修改方法如下 Page IDRecord Offset(Filed 1, Value 1) ... (Filed i, Value i) ... ) 其中PageID指定要操作的Page页Record Offset记录了Record在Page内的偏移位置后面的Field数组记录了需要修改的Field以及修改后的Value。
由于Physiological Logging的方式采用了物理Page中的逻辑记法导致两个问题
1、需要基于正确的Page状态上重放REDO
由于在一个Page内REDO是以逻辑的方式记录了前后两次的修改因此重放REDO必须基于正确的Page状态。然而InnoDB默认的Page大小是16KB是大于文件系统能保证原子的4KB大小的因此可能出现Page内容成功一半的情况。InnoDB中采用了Double Write Buffer的方式来通过写两次的方式保证恢复的时候找到一个正确的Page状态。这部分会在之后介绍Buffer Pool的时候详细介绍。
2、需要保证REDO重放的幂等
Double Write Buffer能够保证找到一个正确的Page状态我们还需要知道这个状态对应REDO上的哪个记录来避免对Page的重复修改。为此InnoDB给每个REDO记录一个全局唯一递增的标号LSN(Log Sequence Number)。Page在修改时会将对应的REDO记录的LSN记录在Page上FIL_PAGE_LSN字段这样恢复重放REDO时就可以来判断跳过已经应用的REDO从而实现重放的幂等。
REDO中记录了什么内容 到MySQL 8.0为止已经有多达65种的REDO记录。用来记录这不同的信息恢复时需要判断不同的REDO类型来做对应的解析。根据REDO记录不同的作用对象可以将这65中REDO划分为三个大类作用于Page作用于Space以及提供额外信息的Logic类型。
作用于Page的REDO 这类REDO占所有REDO类型的绝大多数根据作用的Page的不同类型又可以细分为Index Page REDOUndo Page REDORtree PageREDO等。比如MLOG_REC_INSERTMLOG_REC_UPDATE_IN_PLACEMLOG_REC_DELETE三种类型分别对应于Page中记录的插入修改以及删除。这里还是以MLOG_REC_UPDATE_IN_PLACE为例来看看其中具体的内容 图片解读 前三个带着阴影的是每个redo log都带有的公共字段 其中Type就是MLOG_REC_UPDATE_IN_PLACE类型Space ID和Page Number唯一标识一个Page页这三项是所有REDO记录都需要有的头信息后面的是MLOG_REC_UPDATE_IN_PLACE类型独有的其中Record Offset用给出要修改的记录在Page中的位置偏移Update Field Count说明记录里有几个Field要修改紧接着对每个Field给出了Field编号(Field Number)数据长度Field Data Length以及数据Filed Data。
作用于Space的REDO 这类REDO针对一个Space文件的修改如MLOG_FILE_CREATEMLOG_FILE_DELETEMLOG_FILE_RENAME分别对应对一个Space的创建删除以及重命名。由于文件操作的REDO是在文件操作结束后才记录的因此在恢复的过程中看到这类日志时说明文件操作已经成功因此在恢复过程中大多只是做对文件状态的检查以MLOG_FILE_CREATE来看看其中记录的内容 同样的前三个字段还是TypeSpace ID和Page Number由于是针对space的操作并不作用于page所以这里的Page Number永远是0。在此之后记录了创建的文件flag以及文件名用作重启恢复时的检查。
解读 其实这里记录的是一些对于表的修改。表的正删改查算是。
提供额外信息的Logic REDO 除了上述类型外还有少数的几个REDO类型不涉及具体的数据修改只是为了记录一些需要的信息比如最常见的MLOG_MULTI_REC_END就是为了标识一个REDO组也就是一个完整的原子操作的结束。
REDO是如何组织的 所谓组织方式其实就是我们如何给redo内从记录的磁盘文件保存下来。以便用方便高效的方式在redo中写入、读取、恢复以及清理。 我们这里把redo从上倒下分为三层 逻辑、物理、文件
逻辑REDO层
这一层是真正的REDO内容REDO由多个不同Type的多个REDO记录收尾相连组成有全局唯一的递增的偏移snInnoDB会在全局log_sys中维护当前sn的最大值并在每次写入数据时将sn增加REDO内容长度 解读 偏移量sn可以理解为redo log的物理地址。偏移量snSegment Number是InnoDB存储引擎中redo log的一种全局唯一的递增的数值用于标记redo log中每个记录的位置。 redo log中的每个redo记录都有一个唯一的sn它标记了redo记录在redo log中的位置。因此sn的确可以视为redo log的物理地址。
物理REDO层
原文 磁盘是块设备InnoDB中也用Block的概念来读写数据一个Block的长度OS_FILE_LOG_BLOCK_SIZE等于磁盘扇区的大小512B每次IO读写的最小单位都是一个Block。除了REDO数据以外Block中还需要一些额外的信息下图所示一个Log Block的的组成包括12字节的Block Header前4字节中Flush Flag占用最高位bit标识一次IO的第一个Block剩下的31个个bit是Block编号之后是2字节的数据长度取值在[12508]紧接着2字节的First Record Offset用来指向Block中第一个REDO组的开始这个值的存在使得我们对任何一个Block都可以找到一个合法的的REDO开始位置最后的4字节Checkpoint Number记录写Block时的next_checkpoint_number用来发现文件的循环使用这个会在文件层详细讲解。Block末尾是4字节的Block Tailer记录当前Block的Checksum通过这个值读取Log时可以明确Block数据有没有被完整写完。 Block中剩余的中间498个字节就是REDO真正内容的存放位置也就是我们上面说的逻辑REDO。我们现在将逻辑REDO放到物理REDO空间中由于Block内的空间固定而REDO长度不定因此可能一个Block中有多个REDO也可能一个REDO被拆分到多个Block中如下图所示棕色和红色分别代表Block Header和Tailer中间的REDO记录由于前一个Block剩余空间不足而被拆分在连续的两个Block中。 由于增加了Block Header和Tailer的字节开销在物理REDO空间中用LSN来标识偏移可以看出LSN和SN之间有简单的换算关系
constexpr inline lsn_t log_translate_sn_to_lsn(lsn_t sn) { return (sn / LOG_BLOCK_DATA_SIZE * OS_FILE_LOG_BLOCK_SIZE sn % LOG_BLOCK_DATA_SIZE LOG_BLOCK_HDR_SIZE);}
SN加上之前所有的Block的Header以及Tailer的长度就可以换算到对应的LSN反之亦然。
阿丹解读 第一段中提高的Block概念为
这里提到的Block是InnoDB存储引擎中的一个概念用于管理磁盘上的数据和日志。在InnoDB中一个Block是数据文件和日志文件的最小读写单位也是数据恢复和事务处理的基本单位。
在InnoDB中每个Block都有固定的开头和结尾包含了一些元数据信息例如
Block Header这是每个Block的头部包含了一些状态信息例如是否是第一次写入Flush FlagBlock的编号等。Data Length这是紧跟在Block Header后面的字段表示该Block中数据的长度。First Record Offset这个字段用于指向Block中第一个REDO组的开始位置。在发生事务回滚或数据恢复时可以通过这个偏移量快速找到REDO数据的起始位置。Checkpoint Number这是记录在Block末尾的校验和用于验证数据的完整性。在写入Block时会将写入的下一个checkpoint的编号记录下来以便在读取Log时可以通过比较当前的checkpoint编号和记录的checkpoint编号来检查数据是否被完整写入。Block Tailer这是每个Block的尾部记录了该Block的校验和Checksum用于检查数据是否在传输或存储过程中发生错误。
总的来说Block是InnoDB中用于管理磁盘上数据和日志的一种数据结构它包含了元数据信息和数据本身可以有效地进行数据读写、事务处理和数据恢复等操作。 这里的计算我确实是没看太懂。
文件层
原文 最终REDO会被写入到REDO日志文件中以ib_logfile0、ib_logfile1...命名为了避免创建文件及初始化空间带来的开销InooDB的REDO文件会循环使用通过参数innodb_log_files_in_group可以指定REDO文件的个数。多个文件收尾相连顺序写入REDO内容。每个文件以Block为单位划分每个文件的开头固定预留4个Block来记录一些额外的信息其中第一个Block称为Header Block之后的3个Block在0号文件上用来存储Checkpoint信息而在其他文件上留空 其中第一个Header Block的数据区域记录了一些文件信息如下图所示4字节的Formate字段记录Log的版本不同版本的LOG会有REDO类型的增减这个信息是8.0开始才加入的8字节的Start LSN标识当前文件开始LSN通过这个信息可以将文件的offset与对应的lsn对应起来最后是最长32位的Creator信息正常情况下会记录MySQL的版本。 现在我们将REDO放到文件空间中如下图所示逻辑REDO是真正需要的数据用sn索引逻辑REDO按固定大小的Block组织并添加Block的头尾信息形成物理REDO以lsn索引这些Block又会放到循环使用的文件空间中的某一位置文件中用offset索引 虽然通过LSN可以唯一标识一个REDO位置但最终对REDO的读写还需要转换到对文件的读写IO这个时候就需要表示文件空间的offset他们之间的换算方式如下
const auto real_offset log.current_file_real_offset (lsn - log.current_file_lsn);
切换文件时会在内存中更新当前文件开头的文件offsetcurrent_file_real_offset以及对应的LSNcurrent_file_lsn通过这两个值可以方便地用上面的方式将LSN转化为文件offset。注意这里的offset是相当于整个REDO文件空间而言的由于InnoDB中读写文件的space层实现支持多个文件因此可以将首位相连的多个REDO文件看成一个大文件那么这里的offset就是这个大文件中的偏移。
Redo log中保存的是什么
Redo log中保存的是事务中修改的任何数据具体来说它记录了数据库中数据页的更新内容是一种物理日志。
在redo log buffer和redo log文件中日志以块为单位存储每个块包括以下三个部分 日志块头Header占12字节记录了日志块的一些元信息例如日志块的大小、日志块是否被写入磁盘等。 日志块尾Tail占8字节记录了日志块的校验和Checksum用于检查日志块的完整性。 日志主体Body占492字节记录了数据页的更新内容具体包括数据页的物理地址、修改的类型、修改的字节数、旧值和新值等。
Redo log是由以下信息组成的 日志头信息包括redo log的版本号、事务ID、事务开始时间等信息。 日志主体记录了数据页的更新内容包括数据页的物理地址、修改的类型、修改的字节数、旧值和新值等。 日志尾信息包括redo log的校验和等信息。
总之Redo log是数据库中非常重要的日志文件它能够保证事务的持久性和原子性以及在数据库发生故障时进行数据恢复等操作。
REDO产生
我们知道事务在写入数据的时候会产生REDO一次原子的操作可能会包含多条REDO记录这些REDO可能是访问同一Page的不同位置也可能是访问不同的Page如Btree节点分裂。InnoDB有一套完整的机制来保证涉及一次原子操作的多条REDO记录原子即恢复的时候要么全部重放要不全部不重放这部分将在之后介绍恢复逻辑的时候详细介绍本文只涉及其中最基本的要求就是这些REDO必须连续。InnoDB中通过min-transaction实现简称mtr需要原子操作时调用mtr_start生成一个mtrmtr中会维护一个动态增长的m_log这是一个动态分配的内存空间将这个原子操作需要写的所有REDO先写到这个m_log中当原子操作结束后调用mtr_commit将m_log中的数据拷贝到InnoDB的Log Buffer。
写入InnoDB Log Buffer
高并发的环境中会同时有非常多的min-transaction(mtr)需要拷贝数据到Log Buffer如果通过锁互斥那么毫无疑问这里将成为明显的性能瓶颈。为此从MySQL 8.0开始设计了一套无锁的写log机制其核心思路是允许不同的mtr同时并发地写Log Buffer的不同位置。不同的mtr会首先调用log_buffer_reserve函数这个函数里会用自己的REDO长度原子地对全局偏移log.sn做fetch_add得到自己在Log Buffer中独享的空间。之后不同mtr并行的将自己的m_log中的数据拷贝到各自独享的空间内。
/* Reserve space in sequence of data bytes: */const sn_t start_sn log.sn.fetch_add(len);写入Page Cache 写入到Log Buffer中的REDO数据需要进一步写入操作系统的Page CacheInnoDB中有单独的log_writer来做这件事情。这里有个问题由于Log Buffer中的数据是不同mtr并发写入的这个过程中Log Buffer中是有空洞的因此log_writer需要感知当前Log Buffer中连续日志的末尾将连续日志通过pwrite系统调用写入操作系统Page Cache。整个过程中应尽可能不影响后续mtr进行数据拷贝InnoDB在这里引入一个叫做link_buf的数据结构如下图所示 link_buf是一个循环使用的数组对每个lsn取模可以得到其在link_buf上的一个槽位在这个槽位中记录REDO长度。另外一个线程从开始遍历这个link_buf通过槽位中的长度可以找到这条REDO的结尾位置一直遍历到下一位置为0的位置可以认为之后的REDO有空洞而之前已经连续这个位置叫做link_buf的tail。下面看看log_writer和众多mtr是如何利用这个link_buf数据结构的。这里的这个link_buf为log.recent_written如下图所示 图中上半部分是REDO日志示意图write_lsn是当前log_writer已经写入到Page Cache中日志末尾current_lsn是当前已经分配给mtr的的最大lsn位置而buf_ready_for_write_lsn是当前log_writer找到的Log Buffer中已经连续的日志结尾从write_lsn到buf_ready_for_write_lsn是下一次log_writer可以连续调用pwrite写入Page Cache的范围而从buf_ready_for_write_lsn到current_lsn是当前mtr正在并发写Log Buffer的范围。下面的连续方格便是log.recent_written的数据结构可以看出由于中间的两个全零的空洞导致buf_ready_for_write_lsn无法继续推进接下来假如reserve到中间第一个空洞的mtr也完成了写Log Buffer并更新了log.recent_written*如下图 这时log_writer从当前的buf_ready_for_write_lsn向后遍历log.recent_written发现这段已经连续 因此提升当前的buf_ready_for_write_lsn并将log.recent_written的tail位置向前滑动之后的位置清零供之后循环复用 紧接log_writer将连续的内容刷盘并提升write_lsn。
刷盘
log_writer提升write_lsn之后会通知log_flusher线程log_flusher线程会调用fsync将REDO刷盘至此完成了REDO完整的写入过程。
唤醒用户线程
为了保证数据正确只有REDO写完后事务才可以commit因此在REDO写入的过程中大量的用户线程会block等待直到自己的最后一条日志结束写入。默认情况下innodb_flush_log_at_trx_commit 1需要等REDO完成刷盘这也是最安全的方式。当然也可以通过设置innodb_flush_log_at_trx_commit 2这样只要REDO写入Page Cache就认为完成了写入极端情况下掉电可能导致数据丢失。
大量的用户线程调用log_write_up_to等待在自己的lsn位置为了避免大量无效的唤醒InnoDB将阻塞的条件变量拆分为多个log_write_up_to根据自己需要等待的lsn所在的block取模对应到不同的条件变量上去。同时为了避免大量的唤醒工作影响log_writer或log_flusher线程InnoDB中引入了两个专门负责唤醒用户的线程log_wirte_notifier和log_flush_notifier当超过一个条件变量需要被唤醒时log_writer和log_flusher会通知这两个线程完成唤醒工作。下图是整个过程的示意图 多个线程通过一些内部数据结构的辅助完成了高效的从REDO产生到REDO写盘再到唤醒用户线程的流程下面是整个这个过程的时序图