REJAVA / LEARNING NOTES
事务日志与崩溃恢复
用一次审批的故障时点,讲清 redo、undo、binlog 与两阶段提交。
根据课程与真实学习交流整理;不是逐字稿
本次更新说明
- 整理为公开学习笔记,保留练习与隔日复测。
- 附课后问答:缓冲池与日志缓冲区、binlog 与备份、准备事务的判断依据。
热身 · 一 提交后的疑问 · 二 怎样补回修改 · 三 怎样撤销修改 · 四 另一类变更记录 · 五 何时可靠保存 · 六 提交结果怎样一致 · 七 应用 · 检验 · 表达 · 追问 · 隔日复测
本课要解决什么问题
学完本课,你拿到一个故障场景时能说清结果。场景会交代三件事:事务改了什么,日志有没有可靠保存,数据库在哪一步崩溃。你要说出重启后哪些修改保留、哪些被撤销。你还能解释:客户端收到“提交成功”时,被修改的数据页为什么不一定已经写进数据文件。
第13课讲到:用户确认后再开始短事务,完成必要修改后提交。本课继续追问最后一步——数据库怎样把“已经提交”变成重启后仍可保留的结果。这补充了第11课的durability(持久性),也接上第12课讲过的undo log(撤销日志)。遇到“提交后会不会丢数据”“请求断开后能不能重试”这类常见问题,你要能解释其中的数据库过程,而不是只报出三个日志名。
前置: 知道提交、回滚与同一事务中两条SQL的关系。 必学: 第一至七节、六道选择题和四问表达。第三节后可暂停。第五节给出参数的含义,学习时重点看“日志什么时候可靠保存”,不用背长参数名。检查点和只读配置观察放在折叠拓展里。本课不要求手写恢复工具,也不操作真实库做断电实验。
主读: 官方 InnoDB(事务存储引擎) Recovery(崩溃恢复)。先读“InnoDB(事务存储引擎) Crash Recovery”下的日志重放和未完成事务回滚,再读“Point-in-Time Recovery”。配合本课具体过程阅读,不需要先看强制恢复参数。
热身:把上一课的结尾接过来
先凭记忆回答,再展开核对。这部分也可以稍后再做;隔一段时间再答,更能看出是否真正记住。
第13课:A、B是两个独立连接,各有自己的事务。A持有20号记录的X锁,正在等用户确认;B修改20号时在等A放锁。A没有等B的锁。为什么还不是死锁,怎样调整流程?
B在等A释放20号记录的锁,A在等用户,双方没有形成锁的循环等待。应用展示页面时先普通查询,并结束这次读取事务。用户确认后,应用再开始新事务,锁定读取20号并检查当前状态;若允许审批,就完成两条修改,一起提交或回滚。用户思考的时间因此不再占用20号的锁。参见第13课课后问答。
第13课:题目明确开启死锁检测。数据库要先等锁等待超时,才回滚死锁中的事务吗?
不需要。检测到循环等待后,引擎会选择一个事务回滚。锁等待超时是等得超过时限,没有循环等待也可能发生。
第12课:20号办件被改过以后,数据库凭什么还能还原旧值?
当前记录带有修改事务编号和回滚指针;数据库沿撤销信息可以还原旧行。本课会把这种“往回撤销”与“往前补回修改”区分开。
一、用户已经看到“审批成功”,数据文件却可能还没有写完
“数据库已经提交,为什么还需要日志来恢复?”
先把一次审批的对象说清楚。case_task 是办件表,20号办件原来是“待审批”;approval_record 是审批记录表,原来没有701号记录。20和701是业务记录编号,不是事务编号。
上述初始数据已经提交。业务规则仍沿用第13课:一个办件只能成功审批一次,已经通过的办件不能再新增一份审批记录。用户点击确认后,应用通过数据库连接A开始一个显式事务,取得必要的锁并检查状态。A在这个事务中执行两步:把20号状态改成“已通过”;新增701号审批记录,记录“20号办件通过审批”。两步都成功,应用才请求提交。两张表都是普通的InnoDB(事务存储引擎)表,两步必须一起成功或一起撤销。后面的故障例子彼此独立,每次都从这里的初始数据开始。
| 时点 | 20号办件 | 701号审批记录 |
|---|---|---|
| 事务开始前 | 待审批 | 不存在 |
| 两条SQL执行完,但尚未提交 | A的事务中已经改为已通过 | A的事务中已经插入 |
| 正常提交成功后 | 已通过;以后即使断电重启,也必须还是已通过 | 存在;以后即使断电重启,也必须还在 |
“执行修改”并不等于数据库立刻把每个变化都写到表的数据文件里。InnoDB(事务存储引擎)会把常用的表和索引内容放在内存中,这块内存叫 buffer pool(缓冲池)。数据库按 page(页)读写数据,一页可能容纳多条记录;不必把一页理解成一行。[1]
A把20号改成“已通过”时,数据库先改的是缓冲池里存放20号的那一页。此刻内存里这一页写着“已通过”,磁盘上数据文件里的同一页仍写着“待审批”。像这样内存里已经改了、文件里还没跟上的页,叫 dirty page(脏页)。“脏”只表示这一页还有修改没写回文件,不表示数据坏了,也不表示这笔事务还没提交。[12]
把脏页写回数据文件要做磁盘I/O,数据库不必每改一次就立刻写一次,可以留到后面分批写回。[5]这样做的问题是:内存里的内容在进程退出或机器断电时会丢失。如果20号那一页还没写回就断电,文件里仍是“待审批”。所以数据库必须另外留一份记录,存在断电也不会丢的地方,重启后靠它把修改补回来。下一节讲这份记录。
本课主例只讨论普通事务、健康且可读取的数据文件与日志。暂不讨论磁盘文件损坏或整个存储设备丢失;那类事故需要备份等另外的恢复手段。样例值和故障时点均为教学设定。
先试着自己解释,再看参考回答
InnoDB(事务存储引擎)修改数据时,先改缓冲池里的页面,再找时间分批写回数据文件,所以提交成功时数据页不一定已经写回。数据库另外留了一份断电不丢的日志,重启后可以照着它把修改补回来。断电后结果能不能保住,要看这份日志在提交时是否已经可靠保存。
二、redo:把数据文件来不及保存的修改补回来
“内存里的修改没了,数据库用什么补回已经提交的结果?”
上一节说的那份记录,就是 redo log(重做日志)。InnoDB(事务存储引擎)修改页面的同时,往redo里追加相应的日志,写明哪些记录被改成了什么。本课的审批会追加两类redo:一类对应把20号状态改成已通过,一类对应插入701号审批记录。断电重启后,数据库按redo把这些修改在页面上重新做一遍,所以它叫“重做”日志。redo由引擎按自己的格式编码,不是原样保存那条SQL,也不是保存“请把20号改成已通过”这句中文。[2]
redo也不是一产生就进了磁盘。InnoDB(事务存储引擎)先把它放进内存里的 log buffer(日志缓冲区),之后再写进磁盘上的日志文件。什么时候写、写到哪一步才算真正保存好,第五节专门讲。注意区分两块内存:缓冲池放表和索引的数据页,日志缓冲区放还没写出去的redo。[3]
沿一次故障走完过程
假设审批事务已经完成提交,相关redo及提交协调所需的记录都已经可靠保存,客户端也收到了成功响应。但包含20号办件和701号审批记录的最新数据页,还没有写回表文件。
| 接下来发生什么 | 数据库面对的状态 | 恢复时的动作 |
|---|---|---|
| 机器突然断电 | 内存丢失,数据文件还可能是审批前的页面 | 不能单凭表文件较旧就判断提交失败 |
| 重新启动数据库 | redo日志仍在可靠存储上 | 引擎读取redo,把数据文件里还缺的那些修改重新做到页面上 |
| 这笔事务恢复完成 | 提交结果应该保留 | 20号为已通过,701号审批记录也存在 |
这个自动补回修改的过程,是 crash recovery(崩溃恢复)的一部分。redo为它提供需要重做的信息。数据库不会要求应用重新执行一次审批,去猜上次改过什么。[4]
“先写日志”到底先于什么
WAL(预写日志)规定的是两个动作的先后: 先把redo可靠保存,再把对应的脏页写回数据文件。 拿20号那一页来说,数据库要把它写回文件之前,必须先确认记录“20号改成已通过”的那条redo已经可靠保存。官方固定版本源码里,写修改过的页之前也确实会先等对应的redo刷盘。[10]
为什么要这个顺序?反过来想:如果先把20号那一页写进文件,redo还留在内存里就断电,文件里会出现一个日志里查不到的修改。恢复程序找不到它的记录,就没法判断它属于哪笔事务、该保留还是该撤销。按“先日志、后数据页”的顺序,文件里出现的每个修改,日志里都能找到对应记录。
提交也沿用这个思路:只要这笔事务的redo已经可靠保存,即使数据页还没写回,断电后也能补回来,所以提交不必等数据页写回。这就是第一节“提交了,但数据文件可能还是旧的”能够成立的原因。启用binlog(二进制日志)时,提交还要多走几步,第六节再讲。
拓展:redo会一直增长吗?检查点是什么?
不会。脏页陆续写回文件以后,记录这些修改的redo在恢复时就用不上了。checkpoint(检查点)是日志里的一个位置标记:标记之前的修改都已经写进了数据文件。崩溃恢复时,数据库从这个标记开始往后读redo、补修改,不用从头读;标记之前的旧redo所占空间可以腾出来重新使用。所以redo不是能倒回任意历史时刻的档案;还没越过检查点的redo也不能随手删掉。[5]
先试着自己解释,再看参考回答
redo记录每次页面修改改成了什么。事务已经可靠提交、数据页还没写回就崩溃时,数据库重启后按redo把修改重新做到页面上。脏页写回文件之前,对应的redo必须先可靠保存,这就是预写日志的顺序;它保证文件里的每个修改,日志里都有记录可查。
三、undo:页面上出现过的修改,不一定应该最终保留
“既然redo会补回修改,会不会把失败事务也恢复成成功?”
关键是: redo里存在某次修改,不等于那笔事务已经提交。 事务还在执行时就会产生redo,日志也可能在事务提交前写出去;修改过的数据页也可能在提交前被写回。因此数据库还需要知道哪些事务最终应该保留,哪些修改必须撤销。
undo log(撤销日志)记的是“改之前是什么样、怎样改回去”。回到本课审批:A把20号从“待审批”改成“已通过”,undo记下20号原来是“待审批”;A插入了701号记录,undo记下这条记录是本事务新插入的,撤销时要把它删掉。如果这笔事务需要回滚,InnoDB(事务存储引擎)就按undo把20号改回“待审批”,并删掉701号记录。可以看到,更新和插入的撤销方式不同,undo并不是每次把整行复制一份。[6]
一个与上一节不同的故障时点
另一次审批中,A执行完两条SQL,但还没有请求提交,也没有进入第六节要讲的“准备提交”阶段,数据库就崩溃了。重启恢复分两步: 第一步 ,引擎按已经保存下来的redo把修改重新做到页面上。A的redo有可能在提交之前就已经写进日志文件,这时页面上会重新出现A还没提交的两项修改。 第二步 ,引擎发现A在崩溃时既没提交、也没进入准备阶段,于是按undo撤销A的修改。回滚完成后,20号仍为待审批,701号记录不存在。[4]
所以一次恢复里两种日志各管一段:redo负责把已经记下的页面修改补回来,不管修改来自哪笔事务;undo负责把其中不该保留的事务修改撤掉。不能背成“有redo的全保留,没redo的全回滚”。另外,平时执行ROLLBACK(回滚)也靠undo,不是只有崩溃恢复才用它。
第12课还讲过undo的另一用途:普通查询为了读取旧视图允许的版本,可能需要沿撤销信息还原旧行。因此写事务提交之后,相关更新undo不一定立刻删除,旧视图可能仍然需要它。
先试着自己解释,再看参考回答
redo里的修改可能来自尚未提交的事务。恢复时,数据库既要利用redo重建需要的页面变化,也要判断事务结果,对需要回滚的事务利用undo撤销修改。undo还支持旧版本读取,不能把它简单当成已经没用的错误记录。
这里可以暂停。先能说清“把修改补回来”和“把不应保留的修改撤销”各在做什么,再继续看第三类日志。
四、binlog(二进制日志):把数据变更交给副本或备份恢复流程
“已经有redo,为什么还需要binlog(二进制日志)?”
先分清一个MySQL实例里的两层。上层是MySQL服务器层:接收应用发来的SQL,再调用下层读写表。下层是InnoDB(事务存储引擎):负责把数据存进页面、管理事务。两层在同一个MySQL实例里,不是两台数据库。前面讲的redo和undo都由InnoDB(事务存储引擎)维护; 服务器层自己还维护一类日志 ,叫 binary log(二进制日志),常简称binlog(二进制日志)。
redo是给InnoDB(事务存储引擎)在本机崩溃后补页面用的;binlog(二进制日志)是给“别处”用的。它按事务记下数据发生了哪些变更,典型用途有两个:一是让其他数据库副本照着这些变更做一遍,跟上主库;二是恢复备份以后,补上备份之后发生的变更。[7]
比如上午9点备份时,20号还未审批;9点10分完成了审批。如果需要从9点备份恢复到9点15分,先恢复合适的备份,再按正确位置应用保留下来的binlog(二进制日志),就能补上9点10分的审批事务。前提是备份与日志匹配,所需日志完整、可读;不能只拿一小段binlog(二进制日志)就声称能重建整座数据库。
binlog(二进制日志)也不是“把所有SQL逐句抄下来”。MySQL 8.4默认使用ROW(按行记录)格式,记录受影响行的变更事件。普通不修改数据的SELECT通常不写进binlog(二进制日志)。其他日志格式可以按语句记录,本课先理解用途,不要求背事件编码。[8]
| 要解决的问题 | 主要依靠什么 | 在审批例子里的作用 |
|---|---|---|
| 这笔事务要撤销,或旧查询需要旧版本 | undo log(撤销日志) | 记下20号改之前是“待审批”、701号是本事务新插入的,用来改回去或删掉 |
| 崩溃时数据页没有写完,需要重建页面变化 | redo log(重做日志) | 把redo里已经记下、数据文件里还缺的修改补回页面 |
| 副本要跟上变更,或恢复备份后的业务变更 | binlog(二进制日志) | 在副本或恢复出的数据库上,把20号审批这笔事务的变更再应用一遍 |
这三类日志不是互相替换的三份同样内容。副本重放通常依靠binlog(二进制日志),InnoDB(事务存储引擎)本地页面恢复主要依靠redo,撤销事务与旧版本读取依靠undo;它们回答的问题不同。
拓展:误删已经提交,undo能随时一键撤销吗?
不能。事务一旦提交,就不能再把它当作仍打开的事务执行普通回滚;undo也不保证永久保留全部历史。误删后的恢复要根据备份、保留日志和具体事故设计,不能直接把崩溃恢复等同于恢复任意历史状态。本课只解释恢复材料的作用,不执行回放命令覆盖现有数据。
先试着自己解释,再看参考回答
redo由InnoDB(事务存储引擎)维护,用来在本机崩溃后把修改补回页面;undo也由InnoDB(事务存储引擎)维护,用来撤销修改、给旧查询提供旧版本。binlog(二进制日志)由MySQL服务器层维护,按事务记下数据变更,让副本跟上主库,或在恢复备份后补上之后的变更。三者回答的问题不同;binlog(二进制日志)在默认的ROW(按行记录)格式下记录行的变更,也不是原样抄下全部SQL。
五、“写了日志”不够,还要问日志写到了哪里
“只要收到提交成功,任何配置下断电都不会丢吗?”
“写了日志”可能在说下面三个不同阶段。我们继续用redo说明:
- 还在MySQL的内存中。 InnoDB(事务存储引擎)已经生成日志,并把它放进日志缓冲区。MySQL进程意外退出时,这部分内存内容可能丢失。
- 已经交给操作系统写文件。 MySQL完成了文件写入调用,但内容可能还在操作系统的缓存中。机器突然断电时,这些缓存仍可能丢失。
- 已经完成可靠同步。 MySQL要求把相应日志可靠保存,存储系统也确认完成。在设备正确工作的前提下,断电重启后仍能读到这些日志。
因此,“文件写入调用返回了”,与“日志已经能经受断电”,还需要分开判断。
本课说“日志持久化”或“刷盘完成”,指第三个阶段。后文假定操作系统和存储设备确实完成了它们所报告的同步;仅存在内存中不算完成。
一个参数决定:提交时redo要走到第几个阶段
innodb_flush_log_at_trx_commit 决定事务提交时,这笔事务的redo至少要走到上面哪个阶段,数据库才返回提交成功。没走完的部分交给后台定期处理,默认约每秒一次。[9]
| 参数值 | 返回提交成功时,redo走到了哪个阶段 | 对“收到成功就不丢”意味着什么 |
|---|---|---|
| 1(默认) | 第3阶段:提交时写进日志文件并完成可靠同步 | 在存储如实完成同步的前提下,断电后redo仍在。启用binlog(二进制日志)时,还要另看它自己的同步参数 |
| 2 | 第2阶段:提交时交给操作系统写文件,可靠同步留给后台定期做 | 收到成功时redo可能还在操作系统缓存里;这时断电,最近已返回成功的事务可能丢失 |
| 0 | 可能仍在第1阶段:提交时不要求写文件,写文件和同步都留给后台定期做 | 收到成功时redo可能还在MySQL内存里;这时发生崩溃,最近已返回成功的事务可能丢失 |
常听到的“设成2最多丢一秒”不能当作严格保证:官方说明后台同步不保证恰好每秒一次,可能更频繁,也可能因调度而推迟,可能丢失的范围随之变化。还要注意,这个参数只改变“什么时候把redo可靠保存”。崩溃恢复后,一笔事务的修改仍然要么全部保留、要么全部撤销,不会出现只保留一半的情况。[9]
启用binlog(二进制日志)时,还要单独检查它是否同步
sync_binlog 控制binlog(二进制日志)同步频率。值为1时,提交前同步相应的binlog(二进制日志);值为0时,MySQL不主动按提交要求同步它,而依赖操作系统安排写回。redo与binlog(二进制日志)的参数控制不同文件,不能只看其中一个。[11]
官方建议,在启用binlog(二进制日志)并要求尽可能保证事务持久性与一致性的场景中,使用 innodb_flush_log_at_trx_commit = 1 与 sync_binlog = 1。这有同步I/O成本,实际系统可以把多个事务合并处理日志,不能简单推断“每个事务固定单独写几次磁盘”。
假设审批业务明确要求:客户端已经收到成功的结果,机器断电后也要保留。同事为了减少提交等待,建议把redo参数从1改成2、把binlog(二进制日志)参数从1改成0。我们按三步判断:
- 先看改变了什么。 修改后,提交可能不再等待相关日志可靠同步。应用已经收到成功时,日志还可能留在会因断电丢失的缓存中。
- 再对照业务要求。 断电可能丢失最近的成功审批,这与题目给出的要求冲突。因此不能只因为提交变快就采用这项改动。
- 最后核对证据。 确认实例实际使用的参数、存储是否正确完成同步,并用相同业务负载测量提交延迟,弄清时间花在哪里。没有这些信息,就不能给出具体收益数字。
这个例子里应保留满足可靠性要求的同步策略,再评估其他改进。这里是在解释配置取舍,不修改你的数据库参数。
先试着自己解释,再看参考回答
日志写进内存、交给操作系统和完成可靠同步,是不同阶段。redo提交策略为1、binlog(二进制日志)同步为1,是启用binlog(二进制日志)时官方建议的可靠配置组合;实际保证仍依赖存储正确执行同步。放宽刷盘策略可能减少等待,但要承担最近提交结果在故障后丢失的风险。
六、两类日志怎样对“这笔事务是否提交”达成一致
“InnoDB(事务存储引擎)已经提交了这笔事务,binlog(二进制日志)却没有记全,会有什么问题?”
假设InnoDB(事务存储引擎)已经确认20号的审批事务提交成功,但binlog(二进制日志)没有这笔完整事务,那么依靠binlog(二进制日志)重放的副本或备份恢复流程就可能缺少这次审批。反过来,binlog(二进制日志)记录了完整审批,InnoDB(事务存储引擎)却把这笔事务回滚,也会产生不一致。因此MySQL需要协调InnoDB(事务存储引擎)与binlog(二进制日志)对提交结果的判断。
MySQL用 two-phase commit(两阶段提交)协调这两部分。这里讨论单个MySQL实例里InnoDB(事务存储引擎)与binlog(二进制日志)的配合,不要求应用手工发送两次COMMIT,也不是让你现在配置跨服务分布式事务。官方8.4手册说明这项内部支持一直启用。[7]
把一个事务的关键阶段排出来
这次例子启用binlog(二进制日志),并将 innodb_flush_log_at_trx_commit 和 sync_binlog 都设为1。文件健康,存储能够可靠保存日志。A先执行完两条修改,再向服务器发送提交请求,服务器接下来协调以下步骤。表格展示一笔事务的先后关系;实际系统可能把多笔事务的日志一起写入和同步。
| 阶段 | 哪个部分做了什么 | 这对提交结果意味着什么 |
|---|---|---|
| ① InnoDB(事务存储引擎)准备提交 | InnoDB(事务存储引擎)把本次修改所需的redo和准备状态可靠写入日志,数据页不必在此时写回 | 这笔事务已经准备好,但还要等待协调结果,不能仅凭“准备”认定最终成功 |
| ② 保存完整binlog(二进制日志)事务 | 服务器写入并同步这笔事务的完整binlog(二进制日志)记录,包含结束标记 | 恢复时可以用这个有效记录判断相应的准备事务应完成提交 |
| ③ 完成引擎提交并响应 | 服务器让InnoDB(事务存储引擎)完成提交,随后向客户端响应成功 | 数据库已完成提交;成功响应还需经过网络到达应用,数据页仍可以稍后写回 |
“完整binlog(二进制日志)事务”是什么意思? 恢复程序需要找到这笔事务从开始到结束的有效记录,其中包括表示事务结束的标记。只看到一条“修改20号”的行事件,不能证明整笔事务已经记完。服务器还会根据日志中的事务标识,找到InnoDB(事务存储引擎)里对应的准备事务;这个标识不是办件编号20,也不是审批记录编号701。
如果在①之后、②之前崩溃,恢复时找不到这笔事务的有效完整binlog(二进制日志)提交记录,这笔内部准备事务会回滚。若②已经可靠完成、③尚未结束就崩溃,恢复程序能够根据binlog(二进制日志)里的事务记录,让对应的InnoDB(事务存储引擎)准备事务完成提交。不是看到“引擎最后一步还没走完”,就一律把它回滚。[7]
用一句话记: InnoDB(事务存储引擎)先把事务做到“准备好、只差最后一步”;binlog(二进制日志)里这笔事务写完整了,就算定案。崩溃后,对每笔处于准备状态的事务,看binlog(二进制日志)里有没有它的完整记录:有,就补完提交;没有,就回滚。这样,InnoDB(事务存储引擎)里留下的结果,和binlog(二进制日志)交给副本、备份恢复的内容保持一致。
没有收到成功响应,应用能直接认定失败吗
不能。数据库可能已经提交,只是连接在成功响应到达应用之前断开了。应用看到的是“结果暂时不确定”,不是数据库已经证明回滚。应用需要重新连接处理审批的数据库,核对20号办件及对应审批记录的最终状态。
本课的审批仍沿用第13课的处理方法:在新事务中取得20号记录的锁,重新读取当前状态。如果已经通过,就结束事务并返回已有结果,不再新增审批记录。
为什么用锁定读取,而不是一条普通查询?连接断开时,第一次的事务可能还在数据库里收尾,仍然持有20号记录的锁。按第12、13课的规则,普通查询不等锁,可能读到提交前的“待审批”和“没有701号”;锁定读取要等这把锁释放,拿到锁后读到的才是最终结果。所以普通查询一时没查到701号,不能证明第一次事务已经回滚。处理原则是:先确认业务的最终状态,再决定要不要重试。
这个区别与第13课死锁报错不同:题目若明确数据库已经回滚整个事务,就有明确的失败范围;连接断开只说明通信中断,没有说明数据库最后选择了提交还是回滚。
先试着自己解释,再看参考回答
内部两阶段提交先让InnoDB(事务存储引擎)准备,再可靠记录binlog(二进制日志)事务,最后完成引擎提交。恢复时要结合准备状态与有效binlog(二进制日志)事务判断结果。数据页可以晚于提交写回;客户端没收到成功响应,也不能仅凭这一点断定数据库已经回滚。
七、应用:根据故障时点判断结果
“遇到恢复问题,应该先问日志状态,还是直接把业务再做一遍?”
A.两张表的数据页,写回进度不同
背景: 连接A在同一个事务中,把20号从待审批改成已通过,并新增701号审批记录。两步都成功,数据库完成提交,客户端收到成功响应。必要的redo和完整binlog(二进制日志)事务都已经可靠保存。
故障发生前: 办件表的数据页已经写回,所以文件中20号是已通过;审批记录表的数据页还没写回,所以文件中还没有701号。随后机器断电。没有其他事务修改这些记录,数据文件和日志完整,存储可靠。
- 两张表的数据页进度不同,能否说明数据库只提交了半次审批?请解释原因。
- 重启恢复后,20号状态和701号记录各应怎样?数据库用什么补上尚未写回的变化?
完成后核对过程
不能说只提交了半次审批。这笔事务已经完整提交,页面何时写回是另一件事。InnoDB(事务存储引擎)按redo把审批记录表那一页缺的修改补回来;办件表那一页已经包含修改,恢复不会再额外多出一份审批结果。恢复后20号仍为已通过,701号审批记录存在。应用不需要为了“补记录”再审批一次。
B.看见一条变更记录,就能认定提交了吗
背景: 这是另一次独立审批,从20号待审批、701号不存在开始。连接A在同一个显式事务中把20号改成已通过,并新增701号审批记录。两张表使用InnoDB(事务存储引擎);启用binlog(二进制日志),redo提交参数与binlog(二进制日志)同步参数均为1。
故障发生前: InnoDB(事务存储引擎)已经可靠保存本次redo和准备状态。binlog(二进制日志)只来得及写入本次事务的部分事件,没有写完完整事务及其结束标记;此时服务器崩溃。文件没有发生额外损坏,最后的引擎提交还未执行,也没有其他事务修改这些记录。
- 工程师看到binlog(二进制日志)中有一条修改20号的事件,就认定审批成功。这个判断缺少什么依据?
- 恢复程序应该让这笔内部准备事务提交还是回滚?撤销修改需要哪类日志,恢复后的两条业务记录应怎样?
完成后核对过程
单条事件不能证明整笔事务已经完整保存。题目明确缺少有效完整的binlog(二进制日志)事务记录,因此这笔内部准备事务应回滚;引擎利用undo撤销本次修改。恢复后20号仍是待审批,701号记录不存在。这里有明确的恢复条件,不能把这个结论套到所有“客户端没收到响应”的情况。
可选只读观察:看看连接的实例采用什么日志配置
只在你有查询权限的测试实例运行。下面只读变量,不修改配置、不终止进程,也不模拟断电。若连接失败或没有权限,保留错误并停止;不需要为完成本课去更改生产配置。
SELECT VERSION() AS mysql_version;
SHOW GLOBAL VARIABLES WHERE Variable_name IN (
'log_bin',
'binlog_format',
'innodb_flush_log_at_trx_commit',
'innodb_flush_log_at_timeout',
'sync_binlog'
);
验证思路:把每个实际值与第五节的含义对应起来,说明启用哪些日志、提交是否要求同步。本课不提供查询结果示例,各实例的配置可能不同。即使查到两个参数均为1,也不能单凭配置输出证明硬件可靠或完成了恢复演练。
八、即时检验:先判断,再读解释
每题只问一个主要判断。先选出答案,再展开核对解释;不需要反复刷到全对才继续学习。
- 数据页尚未写回
客户端已收到审批提交成功。恢复所需的日志已可靠保存,但最新数据页还没有写回文件。随后机器断电并重启;文件完整、存储健康,没有后续修改。
问题: 能否仅凭“数据页尚未写回”,就认定这次提交的结果已经丢失?
- A. 一定丢失全部修改
- B. 可以根据日志恢复
- C. 必须重做业务审批
查看答案与解释
答案:B
日志已可靠保存,数据页尚未写回不等于提交结果丢失。InnoDB(事务存储引擎)可以恢复这笔事务的页面变化。
- 修改执行完,事务还没提交
连接A在同一个InnoDB(事务存储引擎)事务中更新办件状态并新增审批记录。两条修改已执行,但A还没有请求提交,也没有进入准备提交阶段,MySQL服务器就崩溃了。恢复时,这笔事务需要回滚。
问题: 即使已经产生redo,引擎仍应怎样撤销这笔事务的修改?
- A. 有重做日志就是提交
- B. 让应用重做整笔审批
- C. 利用撤销信息回滚它
查看答案与解释
答案:C
redo存在不等于提交。恢复程序判断这笔未完成事务需要回滚时,要利用undo撤销其修改。
- 恢复备份之后的变更
维护人员已恢复上午9点的完整备份,还要补上9点到9点15分的业务变更。备份与日志位置匹配,所需日志都已完整保留。
问题: 主要应使用哪类日志补上这个时段的变更?
- A. 使用对应的二进制日志
- B. 使用当前的慢查询日志
- C. 使用旧事务的撤销日志
查看答案与解释
答案:A
备份加上对应完整binlog(二进制日志),可以恢复备份之后的变更。慢查询日志不提供完整变更历史,undo也不是任意历史备份。
- 放宽日志同步要求
实例的 innodb_flush_log_at_trx_commit = 2,sync_binlog = 0。提交返回成功时,相关日志尚未可靠同步。此时机器突然断电,没有等到下一次刷盘。
问题: 能否保证这次已经返回成功的事务不会丢失?
- A. 返回提交成功就不丢失
- B. 日志还在缓存就不丢失
- C. 未完成持久化可能丢失
查看答案与解释
答案:C
返回成功的条件受配置影响。易失缓存不能代替可靠保存,放宽同步策略存在故障后丢失最近事务的风险。
- 准备之后、最后提交之前崩溃
同一个MySQL实例协调InnoDB(事务存储引擎)与binlog(二进制日志)。InnoDB(事务存储引擎)已可靠保存本次redo和准备状态,完整binlog(二进制日志)事务也已可靠保存。最后的引擎提交尚未完成,服务器就崩溃了;相关文件完整,存储健康。
问题: 恢复程序应怎样处理这笔内部准备事务?
- A. 看到准备状态立即回滚
- B. 依据完整记录完成提交
- C. 把这笔事务直接忽略掉
查看答案与解释
答案:B
准备事务要结合有效完整binlog(二进制日志)判断。这里binlog(二进制日志)已可靠记录事务,恢复时可以让对应引擎事务完成提交。
- 提交时连接断开
应用发送提交请求后连接断开,没有收到成功响应。应用没有收到明确的回滚错误,也尚未确认数据库最终的处理结果。
问题: 仅凭连接断开,能否认定数据库已经回滚,并直接重新审批?
- A. 不能认定,需要核对结果
- B. 可以认定,立即重复审批
- C. 可以认定,事务必然撤销
查看答案与解释
答案:A
数据库可能已经提交,只是成功响应没有到达应用。需要核对业务结果,不能把连接断开等同于已回滚。
九、用完整回答检验理解
可以写下来,也可以先口述再整理成文字。每问先说发生了什么,再解释为什么;不需要背日志文件名,也不需要假设自己处理过真实事故。
第一问:为什么不用等全部数据页写回
A在同一个事务中把20号改成已通过,并新增701号审批记录。数据库完成可靠提交,客户端收到成功,但最新数据页尚未写回。随后机器断电;日志与文件完整,存储健康,也没有后续修改。
请回答: 缓冲池和redo各自保存什么?数据库重启后怎样补回本次修改?
第二问:三类日志分别帮谁做什么
下面是三种独立情况:一笔审批事务需要回滚;一笔已经可靠提交的审批,需要在崩溃后补回未写回的页面修改;已恢复上午的备份,需要补上备份之后保留完整日志的审批变更。
请回答: 这三种情况分别主要依靠undo、redo还是binlog(二进制日志)?每类日志为对应过程提供什么信息?
第三问:准备状态是否等于最终提交
同一个MySQL实例中,InnoDB(事务存储引擎)已可靠保存本次redo和准备状态,完整binlog(二进制日志)事务也已可靠保存。最后的引擎提交还没完成,服务器就崩溃了;文件完整,存储健康。应用没有收到成功响应。
请回答: 恢复时应该提交还是回滚这笔事务,依据是什么?为什么应用不能只凭“没收到响应”就直接再审批一次?
第四问:提交更快,是否就值得改参数
审批业务要求机器断电后保留已经确认成功的结果。同事想把 innodb_flush_log_at_trx_commit 从1改成2,把 sync_binlog 从1改成0,希望减少提交等待。
请回答: 这改变了日志可靠保存的哪个时点?对业务有何风险?评估这项改动之前,应该核对哪些事实?不用给出未经测量的性能数字。
练习:请在自己的笔记中写出答案(可附第七节的判断过程),再对照各节的参考回答核对。本课末尾的课后问答整理了这四问里容易说偏的地方。
十、常见追问:把容易混的两件事分开
先自己解释,再展开核对
脏页是否就表示这一页只有未提交事务的数据?
不是。脏页表示内存页面有尚未写回对应数据文件的修改,提交与否是另一件事。已提交事务也可能留下尚未写回的脏页。
redo里记录了修改,为什么恢复时还可能把修改撤销?
事务执行过程中就能产生redo,所以redo不是“只有成功事务才有的清单”。引擎恢复需要的页面变化后,还要按事务结果撤销不应保留的修改。
redo提交策略为1,就能代替sync_binlog为1吗?
不能。两个参数分别控制不同日志的持久化要求。启用binlog(二进制日志)并要求尽可能可靠地协调提交时,两方面都要核对。
内部两阶段提交,是让应用发送两次COMMIT吗?
不是。应用正常提交一次;服务器在内部协调引擎准备、保存binlog(二进制日志)事务和完成引擎提交。
收到死锁回滚错误,与提交时连接断开,是同样的结果吗?
不是。题目若明确死锁导致整事务回滚,就知道此前修改已撤销。连接断开只说明应用没收到结果,事务可能已经提交,需要核对。
十一、隔日复测:换一个业务对象
隔一天再用五分钟回答;约一周后可以换个场景再试。刚看完课就复述,只能算即时练习。
1. 40号、50号两件办件的经办人,在同一事务中都改为9并可靠提交。相关日志完整、存储健康,数据页尚未写回就断电。没有后续修改,恢复后两个办件的经办人各应是几?
两个办件的经办人都应是9。引擎按redo把数据文件里还缺的修改补回页面;不能因为数据文件里的页面较旧,就认定这笔提交丢失。
2. 新事务只执行了更新,尚未提交或进入准备阶段就崩溃。即使有redo,也能把它算作提交成功吗?
不能。redo可能来自尚未提交的事务;需要回滚的修改仍要通过undo撤销。
3. 已恢复昨晚备份,想补上今早保留下来的事务变更,要找什么材料?
要找与备份位置匹配、覆盖需要时段的完整binlog(二进制日志),并按恢复流程重放。redo和undo不能代替这套历史变更材料。
第13课复习:让事务都先修改40号再修改50号,是否就解决了“事务拿着锁等用户五分钟”的问题?
没有。统一顺序减少某类循环等待,但用户操作期间仍持锁,会继续拖住其他事务。应把等待用户移出需要持锁的事务,用户确认后,应用再开启短事务,锁定读取当前状态,检查是否允许修改,然后完成修改并及时提交或回滚。
官方来源与配套材料
本课速查表 · 完整术语表 · 上一课:第13课 · 下一课:第15课。故障时点、业务记录与推演结果是依据官方说明的教学设定,本课没有在数据库上做故障实验。
缓冲池:内存中缓存表、索引页面。
重做日志:日志内容、重放与复用。
日志缓冲区:日志写入文件之前的内存结构。
崩溃恢复与备份后恢复:重放、回滚、恢复流程的区别。
检查点:页面分批写回与恢复起点。
撤销日志:事务回滚与一致性读。
二进制日志:复制、恢复及与InnoDB(事务存储引擎)的提交协调。
日志格式:按行、按语句及混合记录。
redo提交刷盘策略:0/1/2、周期与可靠性前提。
mysql-8.4.0页面刷写源码:修改页写出前先等待所需redo可靠保存。实现细节仅对应该固定版本。
binlog(二进制日志)同步策略:同步时机及与redo的推荐配置。
脏页定义:内存页面已经修改、文件尚未包含相应变化。
学完后,试着沿同一次审批的过程回答两个问题:“页面还没写回,成功响应凭什么发出去”“redo已经补回修改,为什么又需要undo”。隔日回看时先尝试回答,再核对课程里的解释,不只背三个日志名。
课后问答:理解是怎样变化的
问题与结论默认可见。展开后可看理解过程;以下根据真实交流整理,并非逐字稿。
buffer pool(缓冲池)和 log buffer(日志缓冲区)各放什么?断电重启后,数据库从哪里拿到 redo log(重做日志)来补回已提交的修改?
最终结论: 缓冲池缓存数据页(含脏页),日志缓冲区暂存尚未写出的redo。重启后,数据库从磁盘上的redo日志文件读出已经可靠保存的记录,把数据文件里还缺的修改重新做到页面上,所以20号仍是已通过、701号记录也在。正因为只有已存到磁盘上的redo断电后还在,提交时才要求redo先可靠保存。
展开理解过程与易混点
最初理解(归纳): 曾认为缓冲池里同时保存“尚未刷盘的redo”和“尚未写回的修改页”;后半句正确,前半句把两块内存混在了一起。
纠正与解释: 缓冲池放的是表和索引的数据页,改过但还没写回文件的页就是 dirty page(脏页),例如20号所在、内存里已写着“已通过”的那一页;日志缓冲区放的是还没写进日志文件的redo。断电时这两块内存的内容都会丢,所以恢复不是“把缓冲池里没写回的数据再写回去”,那些数据已经不在了。还要分清方向:补回已提交的修改靠redo,撤销未提交的修改才靠 undo log(撤销日志)。
最终结论: 缓冲池缓存数据页(含脏页),日志缓冲区暂存尚未写出的redo。重启后,数据库从磁盘上的redo日志文件读出已经可靠保存的记录,把数据文件里还缺的修改重新做到页面上,所以20号仍是已通过、701号记录也在。正因为只有已存到磁盘上的redo断电后还在,提交时才要求redo先可靠保存。
现有复盘未逐条保留中间追问,本页不补写对话。
binlog(二进制日志)是不是数据的备份?要把数据库恢复到上午9点15分,需要哪两样东西、怎样配合?
最终结论: binlog(二进制日志)不是备份,它补的是“备份之后”的变更,另一个用途是让副本跟上主库。恢复到9点15分要两样一起用:先恢复9点的备份,再从备份对应的位置开始,重放到9点15分为止的binlog(二进制日志);前提是备份与日志匹配、所需日志完整。
展开理解过程与易混点
最初理解(归纳): 曾把binlog(二进制日志)说成“数据的备份”。
纠正与解释: 备份是某个时间点整个数据库的一份副本,例如上午9点的备份;binlog(二进制日志)由MySQL服务器层按事务记下数据发生了哪些变更,例如9点10分“20号改成已通过、插入701号”这笔事务。两者作用不同:只有一段binlog(二进制日志)、没有备份,重建不出整座数据库。
最终结论: binlog(二进制日志)不是备份,它补的是“备份之后”的变更,另一个用途是让副本跟上主库。恢复到9点15分要两样一起用:先恢复9点的备份,再从备份对应的位置开始,重放到9点15分为止的binlog(二进制日志);前提是备份与日志匹配、所需日志完整。
现有复盘未逐条保留中间追问,本页不补写对话。
undo log(撤销日志)和 redo log(重做日志)各自记的是什么?
最终结论: undo记“改之前是什么、怎样改回去”:20号原来是“待审批”,701号是本事务新插入的、撤销时要删掉;它用于回滚,也给旧查询提供旧版本。redo记页面上哪些记录被改成了什么,用于崩溃后把已记下的修改重新做到页面上。审批事务需要回滚靠undo,已提交审批在崩溃后补回未写回的页面修改靠redo,恢复备份后补上之后的审批靠binlog(二进制日志)。
展开理解过程与易混点
最初理解(归纳): 曾把undo描述为“记录有哪些更改、事务编号,一次往上撤回去”,把redo描述为“记录修改数据的操作”;同时正确指出SELECT这类不修改数据的查询不会进redo。
纠正与解释: undo的核心不是事务编号,而是“改之前是什么、怎样改回去”;事务编号和沿版本链查找是第12课讲的辅助信息。redo也不是原样保存SQL或“操作”,而是由引擎按自己的格式记下页面上哪些记录被改成了什么。
最终结论: undo记“改之前是什么、怎样改回去”:20号原来是“待审批”,701号是本事务新插入的、撤销时要删掉;它用于回滚,也给旧查询提供旧版本。redo记页面上哪些记录被改成了什么,用于崩溃后把已记下的修改重新做到页面上。审批事务需要回滚靠undo,已提交审批在崩溃后补回未写回的页面修改靠redo,恢复备份后补上之后的审批靠binlog(二进制日志)。
现有复盘未逐条保留中间追问,本页不补写对话。
崩溃时,一笔事务在InnoDB(事务存储引擎)里处于准备状态,完整binlog(二进制日志)事务已经可靠保存。恢复时提交还是回滚?依据是什么?
最终结论: 应该提交。依据是:这笔事务在InnoDB(事务存储引擎)里已处于“准备好、只差最后一步”的状态,而binlog(二进制日志)里有它的完整记录,恢复时就让对应的准备事务补完提交;如果没有完整记录,才会回滚。
展开理解过程与易混点
最初理解(归纳): 曾判断“应该提交”,结论正确,但给出的依据是“文件完整、存储健康、日志可靠写到了磁盘上”。
纠正与解释: 文件完整、存储健康是题目给的前提,用来排除磁盘损坏这类意外,不是数据库决定提交还是回滚的依据。真正的规则只看binlog(二进制日志)里有没有这笔事务从开始到结束标记都在的完整记录:找到了,副本和备份恢复以后都会重放这笔事务,所以InnoDB(事务存储引擎)也补完提交;找不到,InnoDB(事务存储引擎)就回滚,两边同样一致。
最终结论: 应该提交。依据是:这笔事务在InnoDB(事务存储引擎)里已处于“准备好、只差最后一步”的状态,而binlog(二进制日志)里有它的完整记录,恢复时就让对应的准备事务补完提交;如果没有完整记录,才会回滚。
现有复盘未逐条保留中间追问,本页不补写对话。
提交时连接断开,应用没有收到成功响应。应用接下来应该怎样处理这次审批?
最终结论: “没收到响应”不等于“没提交”。应用应重新连接数据库,开启新事务,锁定读取20号办件并查看当前状态:如果已经是“已通过”,就结束事务、返回已有结果,不再新增审批记录;如果仍是“待审批”,才按正常流程审批。先确认业务的最终状态,再决定要不要重试,否则可能多出一条审批记录。
展开理解过程与易混点
最初理解(归纳): 曾正确指出没收到响应可能只是连接在响应返回途中断开,不能说明事务没提交,但没有说出应用接下来的处理步骤。
纠正与解释: 第一次的事务可能还在数据库里收尾、仍拿着20号的锁。普通查询不等锁,可能读到提交前的“待审批”和“没有701号”;锁定读取要等这把锁释放,拿到锁后读到的才是最终结果。所以普通查询一时没查到701号,不能证明第一次事务已经回滚。
最终结论: “没收到响应”不等于“没提交”。应用应重新连接数据库,开启新事务,锁定读取20号办件并查看当前状态:如果已经是“已通过”,就结束事务、返回已有结果,不再新增审批记录;如果仍是“待审批”,才按正常流程审批。先确认业务的最终状态,再决定要不要重试,否则可能多出一条审批记录。
现有复盘未逐条保留中间追问,本页不补写对话。
把 innodb_flush_log_at_trx_commit 从1改成2、sync_binlog 从1改成0,会给“成功后不能丢”的业务带来什么风险?评估这项改动前要核对哪些事实?
最终结论: 风险是:可能丢失最近已经返回成功的事务,与“成功后不能丢”的要求冲突,所以这个场景下应保留两个参数都为1的同步策略。评估这类改动前要核对:实例实际使用的参数值、存储是否真的按要求完成同步,以及在相同业务负载下提交延迟到底花在哪里;没有测量,就不给出“能快多少”的数字。
展开理解过程与易混点
最初理解(归纳): 曾正确说出改变的是日志可靠保存的时间点、对这类业务风险很大,但把可能的损失说成“丢失一到两条命令”,也没有说出评估前要核对的事实。
纠正与解释: 参数为2时,redo在提交时只交给了操作系统,可靠同步由后台大约每秒做一次;断电时还没同步的那部分事务会丢。官方不保证这个范围严格是一秒。sync_binlog 改成0时,binlog(二进制日志)也不再按提交要求同步。损失的单位是“已经返回成功的事务”,不是“命令”。
最终结论: 风险是:可能丢失最近已经返回成功的事务,与“成功后不能丢”的要求冲突,所以这个场景下应保留两个参数都为1的同步策略。评估这类改动前要核对:实例实际使用的参数值、存储是否真的按要求完成同步,以及在相同业务负载下提交延迟到底花在哪里;没有测量,就不给出“能快多少”的数字。
现有复盘未逐条保留中间追问,本页不补写对话。