ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

浅析MySQL事务中的redo与undo

浅析MySQL事务中的redo与undo 我们都知道事务有4种特性原子性、一致性、隔离性和持久性在事务中的操作要么全部执行要么全部不做这就是事务的目的。事务的隔离性由锁机制实现原子性、一致性和持久性由事务的redo 日志和undo 日志来保证。所以本篇文章将讨论关于事务中的redo和undo的几个问题redo 日志与undo日志分别是什么redo 如何保证事务的持久性undo log 是否是redo log的逆过程redo logRedo 的类型重做日志(redo log)用来保证事务的持久性即事务ACID中的D。实际上它可以分为以下两种类型物理Redo日志逻辑Redo日志在InnoDB存储引擎中大部分情况下 Redo是物理日志记录的是数据页的物理变化。而逻辑Redo日志不是记录页面的实际修改而是记录修改页面的一类操作比如新建数据页时需要记录逻辑日志。关于逻辑Redo日志涉及更加底层的内容这里我们只需要记住绝大数情况下Redo是物理日志即可DML对页的修改操作均需要记录Redo.Redo 的作用Redo log的主要作用是用于数据库的崩溃恢复Redo 的组成Redo log可以简单分为以下两个部分一是内存中重做日志缓冲 (redo log buffer),是易失的在内存中二是重做日志文件 (redo log file)是持久的保存在磁盘中什么时候写Redo?上面那张图简单地体现了Redo的写入流程这里再细说下写入Redo的时机在数据页修改完成之后在脏页刷出磁盘之前写入redo日志。注意的是先修改数据后写日志redo日志比数据页先写回磁盘聚集索引、二级索引、undo页面的修改均需要记录Redo日志。Redo的整体流程下面以一个更新事务为例宏观上把握redo log 流转过程如下图所示第一步先将原始数据从磁盘中读入内存中来修改数据的内存拷贝第二步生成一条重做日志并写入redo log buffer记录的是数据被修改后的值第三步当事务commit时将redo log buffer中的内容刷新到 redo log file对 redo log file采用追加写的方式第四步定期将内存中修改的数据刷新到磁盘中redo如何保证 事务的持久性InnoDB是事务的存储引擎其通过Force Log at Commit 机制实现事务的持久性即当事务提交时先将 redo log buffer 写入到 redo log file 进行持久化待事务的commit操作完成时才算完成。这种做法也被称为Write-Ahead Log(预先日志持久化)在持久化一个数据页之前先将内存中相应的日志页持久化。为了保证每次日志都写入redo log file在每次将redo buffer写入redo log file之后默认情况下InnoDB存储引擎都需要调用一次fsync操作,因为重做日志打开并没有 O_DIRECT选项所以重做日志先写入到文件系统缓存。为了确保重做日志写入到磁盘必须进行一次 fsync操作。fsync是一种系统调用操作其fsync的效率取决于磁盘的性能因此磁盘的性能也影响了事务提交的性能也就是数据库的性能。(O_DIRECT选项是在Linux系统中的选项使用该选项后对文件进行直接IO操作不经过文件系统缓存直接写入磁盘)上面提到的Force Log at Commit机制就是靠InnoDB存储引擎提供的参数innodb_flush_log_at_trx_commit来控制的该参数可以控制 redo log刷新到磁盘的策略设置该参数值也可以允许用户设置非持久性的情况发生具体如下当设置参数为1时默认为1表示事务提交时必须调用一次fsync操作最安全的配置保障持久性当设置参数为2时则在事务提交时只做write操作只保证将redo log buffer写到系统的页面缓存中不进行fsync操作因此如果MySQL数据库宕机时 不会丢失事务但操作系统宕机则可能丢失事务当设置参数为0时表示事务提交时不进行写入redo log操作这个操作仅在master thread 中完成而在master thread中每1秒进行一次重做日志的fsync操作因此实例 crash 最多丢失1秒钟内的事务。master thread是负责将缓冲池中的数据异步刷新到磁盘保证数据的一致性fsync和write操作实际上是系统调用函数在很多持久化场景都有使用到比如 Redis 的AOF持久化中也使用到两个函数。fsync操作 将数据提交到硬盘中强制硬盘同步将一直阻塞到写入硬盘完成后返回大量进行fsync操作就有性能瓶颈而write操作将数据写到系统的页面缓存后立即返回后面依靠系统的调度机制将缓存数据刷到磁盘中去,其顺序是user buffer—— page cache——disk。除了上面谈到的Force Log at Commit机制保证事务的持久性实际上重做日志的实现还要依赖于mini-transaction。Redo在InnoDB中是如何实现的与mini-transaction的联系Redo的实现实则跟mini-transaction紧密相关mini-transaction是一种InnoDB内部使用的机制通过mini-transaction来保证并发事务操作下以及数据库异常时数据页中数据的一致性但它不属于事务。为了使得mini-transaction保证数据页数据的一致性mini-transaction必须遵循以下三种协议The FIX RulesWrite-Ahead LogForce-log-at-commitThe FIX Rules修改一个数据页时需要获得该页的x-latch(排他锁)获取一个数据页时需要该页的s-latch(读锁或者称为共享锁) 或者是 x-latch持有该页的锁直到修改或访问该页的操作完成。Write-Ahead Log在前面阐述中就提到了Write-Ahead Log(预先写日志)。在持久化一个数据页之前必须先将内存中相应的日志页持久化。每个页都有一个LSN(log sequence number)代表日志序列号LSN占用8字节单调递增), 当一个数据页需要写入到持久化设备之前要求内存中小于该页LSN的日志先写入持久化设备那为什么必须要先写日志呢可不可以不写日志直接将数据写入磁盘原则上是可以的只不过会产生一些问题数据修改会产生随机IO但日志是顺序IOappend方式顺序写是一种串行的方式这样才能充分利用磁盘的性能。Force-log-at-commit这一点也就是前文提到的如何保证事务的持久性的内容这里再次总结一下与上面的内容相呼应。在一个事务中可以修改多个页Write-Ahead Log 可以保证单个数据页的一致性但是无法保证事务的持久性Force-log-at-commit 要求当一个事务提交时其产生所有的mini-transaction 日志必须刷新到磁盘中若日志刷新完成后在缓冲池中的页刷新到持久化存储设备前数据库发生了宕机那么数据库重启时可以通过日志来保证数据的完整性。重做日志的写入流程上图表示了重做日志的写入流程每个mini-transaction对应每一条DML操作比如一条update语句其由一个mini-transaction来保证对数据修改后产生redo1首先将其写入mini-transaction私有的Buffer中update语句结束后将redo1从私有Buffer拷贝到公有的Log Buffer中。当整个外部事务提交时将redo log buffer再刷入到redo log file中。undo logundo log的定义undo log主要记录的是数据的逻辑变化为了在发生错误时回滚之前的操作需要将之前的操作都记录下来然后在发生错误时才可以回滚。undo log的作用undo是一种逻辑日志有两个作用用于事务的回滚MVCC关于MVCC(多版本并发控制)的内容这里就不多说了本文重点关注undo log用于事务的回滚。undo日志只将数据库逻辑地恢复到原来的样子在回滚的时候它实际上是做的相反的工作比如一条INSERT 对应一条 DELETE对于每个UPDATE,对应一条相反的 UPDATE,将修改前的行放回去。undo日志用于事务的回滚操作进而保障了事务的原子性。undo log的写入时机DML操作修改聚簇索引前记录undo日志二级索引记录的修改不记录undo日志需要注意的是undo页面的修改同样需要记录redo日志。undo的存储位置在InnoDB存储引擎中undo存储在回滚段(Rollback Segment)中,每个回滚段记录了1024个undo log segment而在每个undo log segment段中进行undo 页的申请在5.6以前Rollback Segment是在共享表空间里的5.6.3之后可通过 innodb_undo_tablespace设置undo存储的位置。undo的类型在InnoDB存储引擎中undo log分为insert undo logupdate undo loginsert undo log是指在insert 操作中产生的undo log因为insert操作的记录只对事务本身可见对其他事务不可见。故该undo log可以在事务提交后直接删除不需要进行purge操作。而update undo log记录的是对delete 和update操作产生的undo log该undo log可能需要提供MVCC机制因此不能再事务提交时就进行删除。提交时放入undo log链表等待purge线程进行最后的删除。补充purge线程两个主要作用是清理undo页和清除page里面带有Delete_Bit标识的数据行。在InnoDB中事务中的Delete操作实际上并不是真正的删除掉数据行而是一种Delete Mark操作在记录上标识Delete_Bit而不删除记录。是一种假删除,只是做了个标记真正的删除工作需要后台purge线程去完成。undo log 是否是redo log的逆过程undo log 是否是redo log的逆过程其实从前文就可以得出答案了undo log是逻辑日志对事务回滚时只是将数据库逻辑地恢复到原来的样子而redo log是物理日志记录的是数据页的物理变化显然undo log不是redo log的逆过程。redo undo总结下面是redo log undo log的简化过程便于理解两种日志的过程假设有A、B两个数据值分别为1,2. 1. 事务开始 2. 记录A1到undo log 3. 修改A3 4. 记录A3到 redo log 5. 记录B2到 undo log 6. 修改B4 7. 记录B4到redo log 8. 将redo log写入磁盘 9. 事务提交实际上在insert/update/delete操作中redo和undo分别记录的内容都不一样量也不一样。在InnoDB内存中一般的顺序如下写undo的redo写undo修改数据页写Redo小结本文分析了事务中的redo和undo日志参考了一些资料书籍整理得出可能有些地方表述的不清楚。如有不对之处欢迎指出。参考资料 鸣谢MySQL技术内幕InnoDB存储引擎第2版MySQL内核InnoDB存储引擎 卷1InnoDB 日志/回滚段/崩溃恢复实现详解MySQL · 引擎特性 · InnoDB redo log漫游MySQL的undo,redo,二阶段提交思维导图作者pjmike链接https://www.jianshu.com/p/20e10ed721d0来源简书著作权归作者所有。商业转载请联系作者获得授权非商业转载请注明出处。## 一、数据页Page ### 1.1 定义 - InnoDB 磁盘和内存之间读写的最小单位默认 **16KB**。 - 不是按行读写而是**按页读写**。查一行也会加载整个 16KB 页到 Buffer Pool。 ### 1.2 页内结构 - 文件头File Header - 页头Page Header - 用户记录行数据 - 空闲空间 - 页目录Page Directory - 文件尾File Trailer ### 1.3 查找与连接 - 页内记录用单向链表按主键串联页目录用槽位做二分查找。 - 页间双向链表B 树叶子节点就是数据页非叶子节点存索引键 页号。 ### 1.4 为什么是 16KB - 磁盘 IO、内存占用、树高度之间的折中。 - 三层 B 树约能存 2000 万行。 - 页太小 → 树变高、IO 次数多页太大 → 单次 IO 浪费、内存碎片。 ### 1.5 页类型 - 数据页INDEX - Undo 页 - 系统页 - 事务数据页 ⚠️ 坑平时说的数据页通常指 B 树的索引页但页还有 Undo 页、系统页、事务数据页等类型。 --- ## 二、三大日志职责对照 | 日志 | 归属层 | 存什么 | 作用 | |---|---|---|---| | **undo log** | InnoDB 引擎层 | 旧值 / 反向操作逻辑 | 事务回滚 MVCC 历史版本 | | **redo log** | InnoDB 引擎层 | 页的变更物理/逻辑 | 崩溃恢复、持久性 | | **binlog** | MySQL Server 层 | 逻辑变更事件SQL 或行变化 | 主从复制 时间点恢复 | ### 2.1 undo log 细节 - 作用一事务回滚记录数据修改前的旧值回滚时反向操作。 - 作用二MVCC 多版本读读已提交/可重复读下其他事务读到的是 undo 里的历史版本。 - 不负责把数据持久化到磁盘负责回到过去。 ### 2.2 redo log 细节 - 作用崩溃恢复。事务提交时先写 redo log顺序写、快再慢慢刷脏页到磁盘随机写、慢即 WALWrite-Ahead Logging。 - 宕机重启后用 redo log 重放已提交但未刷盘的数据。 ### 2.3 binlog 细节 - 记录逻辑变更事件用于主从复制和基于时间点的恢复。 - 归属 MySQL Server 层与存储引擎无关。 ⚠️ 坑 1**redo log 存的不是回滚 SQL**回滚 SQL 是 undo log 的事。 ⚠️ 坑 2**binlog 不是从 redo log buffer 写出来的**两者是完全独立的两套日志。 --- ## 三、redo log vs binlog 写入路径重点防坑 ### 3.1 两条并行链路数据不互相流转数据修改 ──┬── redo log buffer ── redo log fileib_logfile循环写└── binlog cache ── binlog filebinlog.000001…追加写text### 3.2 对比表 | | redo log | binlog | |---|---|---| | 归属层 | InnoDB 引擎层 | MySQL Server 层 | | 内存缓冲 | redo log buffer全局一份 | binlog cache每线程一份 | | 落盘文件 | redo log file | binlog file | | 写入方式 | 循环写空间固定 | 追加写不断切新文件 | ⚠️ 坑 3redo log buffer 只服务 redo log file**不会变成 binlog**。 --- ## 四、事务提交两阶段提交2PC ### 4.1 提交顺序修改数据 → 写 redo log buffer状态标记 prepare写 binlog → binlog cache → 刷到 binlog file提交事务 → redo log 状态改为 committext### 4.2 崩溃恢复判断 判断依据**binlog 是否完整写完。** | 崩溃场景 | 恢复动作 | |---|---| | redo 已 preparebinlog 未写完整 | **回滚**该事务 | | redo 已 preparebinlog 已写完整 | **提交**该事务 | ### 4.3 目的 - 让 redo log 和 binlog 逻辑一致要么都生效要么都不生效。 - 否则主从/恢复会出问题。 --- ## 五、binlog 三种格式 ### 5.1 STATEMENT语句级 - 记录原始 SQL 语句本身 sql UPDATE users SET name B WHERE id 1;优点日志小。缺点NOW()、RAND()、LIMIT 不带 ORDER BY 等在主从库执行结果可能不一致。5.2 ROW行级MySQL 5.7 默认记录每一行被改成什么样不记 SQL。优点主从一致性强不依赖 SQL 语义。缺点一条批量 UPDATE 可能产生海量 binlog日志大。5.3 MIXEDMySQL 自动在 STATEMENT 和 ROW 之间切换。一般用 STATEMENT遇到不安全的语句改用 ROW。5.4 对比表格式记录内容优点缺点STATEMENT原始 SQL 语句日志小部分函数/语句可能主从不一致ROW5.7 默认每行改成什么样主从一致性强批量 UPDATE 日志可能巨大MIXED自动在两者间切换折中行为依赖语句类型六、一条 UPDATE 三个 log 各写什么执行sqlUPDATE users SET name B WHERE id 1; -- 原值 name A日志记录内容undo logid1 的 name 原值是 A回滚时改回 Aredo log某数据页某偏移处值从 A 改成 B用于重放binlogROW 格式id1 这行 name 从 A 变 B 的行事件STATEMENT 格式那条 UPDATE SQL七、一句话总结undo log记旧值 → 回滚 MVCCredo log记页变更 → 崩溃恢复持久性binlog记逻辑变更 → 主从复制 归档恢复三种 log 各司其职互不替代redo 管崩溃恢复undo 管回滚和 MVCCbinlog 管主从复制和归档。
返回列表