REJAVA / LEARNING NOTES
第14课速查:事务日志与崩溃恢复
随时回查术语、规则和示例;完整解释见对应课程。
根据课程材料整理
本次更新说明
- 整理为公开学习笔记,保留要点、对照表与练习入口。
先记住这次审批
20号办件原来待审批,701号审批记录原来不存在,初始数据已经提交。一个办件只能成功审批一次。用户确认后,应用在同一事务里把20号改成已通过,再新增701号记录。成功时两步一起提交,失败时一起撤销。下文只讨论健康存储上的普通InnoDB(事务存储引擎)事务,记录编号不是事务编号。
第13课解决“用户思考期间不要一直持锁”;本课继续解释提交后的结果怎样保存。日志机制不会替应用检查审批状态,也不会自动设计请求去重规则。
数据页与日志分别在哪里
| 名称 | 具体保存什么 | 容易混淆的地方 |
|---|---|---|
| buffer pool(缓冲池) | InnoDB(事务存储引擎)在内存中缓存表和索引的页面,修改可以先发生在这里。 | 一页可以容纳多条记录;内存里的修改不等于已经写回数据文件。 |
| dirty page(脏页) | 某个内存页已经修改,但数据文件中的对应页还没有包含这些变化。 | 脏表示待写回。已提交事务也可能留下脏页,不表示数据损坏。 |
| log buffer(日志缓冲区) | 内存中等待写到日志文件的redo记录。 | 它不是保存业务数据页的缓冲池;机器断电时,易失内存不能保住日志。 |
日志的三个阶段要分开:①还在MySQL进程内存中;②已交给操作系统写文件,可能还在操作系统缓存里;③已可靠同步到存储。本课的“刷盘完成”指存储已经可靠保存日志,并假定设备正确兑现同步承诺。
三类日志回答三个问题
| 日志 | 主要用途 | 对应审批的哪一步 |
|---|---|---|
| undo log(撤销日志) | 记下“改之前是什么、怎样改回去”,用来撤销事务修改;普通一致性读也靠它还原旧行。 | 如果事务需要回滚,把20号恢复为先前状态,并撤销本次新增的701号记录。 |
| redo log(重做日志) | 记下页面上哪些记录被改成了什么;InnoDB(事务存储引擎)崩溃重启后,照着它把修改重新做到页面上。 | 如果审批已经可靠提交,但页面还没写回,重启后补回日志中记录的修改。 |
| binlog(二进制日志) | 由MySQL服务器层维护,按事务记下数据变更,让副本跟上主库、恢复备份后补上之后的变更,并参与提交协调。 | 恢复9点的备份后,用匹配的完整日志补上9点10分的审批。 |
redo在事务执行时就会产生,因此“有redo”不能证明已经提交。崩溃恢复分两步:先按已保存的redo把修改补回页面(可能包括未提交事务的修改),再用undo撤销没有提交的事务。binlog(二进制日志)默认按受影响行记录变更,不是逐字保存所有SQL。
WAL(预写日志)要求:先把redo可靠保存,再把对应的脏页写回数据文件。这样文件里出现的每个修改,日志里都能查到记录,恢复时才能判断保留还是撤销。同理,提交只要求redo可靠保存,数据页可以随后分批写回。
用故障时点判断结果
以下情况彼此独立,每次从上面的初始数据开始。相关文件完整、存储可靠,没有其他事务后来修改这些记录。启用binlog(二进制日志),innodb_flush_log_at_trx_commit = 1,sync_binlog = 1。
| 崩溃发生在哪里 | 已经可靠留下什么 | 恢复这笔事务时怎么办 |
|---|---|---|
| 事务完成提交,客户端收到成功;数据页尚未写回。 | 本次修改所需的redo和完整binlog(二进制日志)事务已经可靠保存。 | 保留提交结果。InnoDB(事务存储引擎)按redo补回数据文件里还缺的修改,20号已通过、701号存在。 |
| 只执行完两条修改,还没有提交或进入准备提交阶段。 | 可能已有本次修改的redo,但没有完成提交。 | 需要回滚。InnoDB(事务存储引擎)利用undo撤销不应保留的修改。 |
| InnoDB(事务存储引擎)已经准备好,但崩溃后找不到对应的完整binlog(二进制日志)事务。 | 有内部准备状态,没有匹配的有效完整binlog(二进制日志)事务记录。 | 不能把准备状态当成成功;在本课的内部协调场景中回滚。 |
| 完整binlog(二进制日志)已经可靠保存,引擎最后提交步骤尚未完成。 | 有内部准备状态,也有匹配的有效完整binlog(二进制日志)事务记录。 | 根据binlog(二进制日志)中的事务记录,让对应的InnoDB(事务存储引擎)准备事务完成提交。 |
两阶段提交协调的是同一个MySQL实例中的InnoDB(事务存储引擎)与binlog(二进制日志)。应用正常提交一次,不需要发送两次COMMIT。“完整记录”要求事务从开始到结束都有效,包括结束标记;一条行变更事件不足以证明整笔事务已经写完。
一句话:InnoDB(事务存储引擎)先做到“准备好、只差最后一步”,binlog(二进制日志)写完整就算定案。崩溃后看binlog(二进制日志)里有没有这笔事务的完整记录:有就补完提交,没有就回滚。
两个同步参数不能互相代替
| 参数与值 | 返回提交成功时,日志走到哪一步 | 怎样评价它 |
|---|---|---|
innodb_flush_log_at_trx_commit = 1 | 已写进日志文件并完成可靠同步(第3阶段)。 | 这是可靠配置的一部分;仍需正确的存储同步保证。 |
innodb_flush_log_at_trx_commit = 2 | 已交给操作系统写文件,可能还在操作系统缓存里(第2阶段);可靠同步留给后台约每秒一次。 | 断电时可能丢失尚未刷盘的最近事务;不能把约一秒说成严格上限。 |
innodb_flush_log_at_trx_commit = 0 | 可能还在MySQL内存里(第1阶段);写文件和同步都留给后台约每秒一次。 | 日志甚至可能还在进程内存中,故障时最近事务可能丢失。 |
sync_binlog = 1 | 提交前已同步这笔事务的binlog(二进制日志)。 | 启用binlog(二进制日志)时,与redo提交参数为1配合,是官方建议的可靠配置。 |
sync_binlog = 0 | MySQL不要求提交时同步binlog(二进制日志),由操作系统安排写回。 | 成功响应到达应用时,完整事务日志仍可能没有可靠保存。 |
放宽同步要求可能减少提交等待,但是否更快、快多少需要测量;业务能否接受最近提交结果丢失,也要先明确。配置查询不能代替恢复演练,更不能证明硬件一定可靠。
应用没收到成功,不代表数据库已经回滚
数据库可能已经提交,只是成功响应在网络断开时没有到达应用。应用应重新连接处理审批的数据库,核对办件状态及对应审批记录。重试处理时,仍在新事务中取得20号的锁并重读状态:已经通过就结束事务并返回已有结果,不再增加第二份记录。要用锁定读取,不用普通查询:原事务可能还在收尾、仍持有20号的锁,普通查询不等锁,可能读到提交前的旧状态;锁定读取等锁释放后才读到最终结果。所以普通查询一时没查到701号,不能证明原事务已回滚。
第13课若明确数据库因死锁回滚整个事务,就知道这次修改已经撤销。提交时连接断开只提供通信失败的信息,不能当成同样的回滚证明。
一分钟表达
InnoDB(事务存储引擎)可以先修改内存中的数据页,随后再把页面写回文件。为了故障后不丢,它用redo记下每次页面修改,重启后照着补回;需要回滚时则利用undo撤销修改,undo也支持旧版本读取。binlog(二进制日志)记录可重放的事务变更,用于副本和备份后的恢复。启用binlog(二进制日志)后,数据库还会协调它与InnoDB(事务存储引擎)的提交结果。可靠保存日志能让数据页晚些写回,但提交能提供多强的保证,取决于同步策略和存储条件;应用没收到成功响应,也不能据此认定事务失败。