开发转兼职DBA(三):数据库起不来了------WAL、事务与redo日志
数据库的redo日志损坏,数据库无法打开。没有RMAN备份。这是我第一次被逼着理解数据库底层原理------为什么redo日志坏了,整个数据库就废了?答案在WAL。
文章目录
事故
某天,个人Oracle数据库启动时报错:
ORA-00312: online log 1 thread 1: '/u01/app/oradata/redo01.log'
ORA-00313: open failed for members of log group 1 of thread 1
redo日志损坏。数据库无法打开。
没有RMAN备份。没有任何备份。
我的第一反应:完了。
为什么redo坏了就起不来
要理解这个问题,得先理解数据库写入数据的方式。
直接写磁盘有什么问题
假设数据库直接把修改写到数据文件:
用户执行 UPDATE kc22 SET xm='李四' WHERE id=1
↓
数据库直接把修改写到 kc22.dbf 数据文件
问题:如果写到一半断电了怎么办?数据文件里的数据可能写了一半------旧的值被覆盖了,新的值没写完。数据不一致。数据库再启动时,不知道这条数据到底改没改过。
WAL:先写日志,再写数据
数据库的解决方案是WAL(Write-Ahead Logging)------先写日志,再写数据。
用户执行 UPDATE kc22 SET xm='李四' WHERE id=1
↓
1. 先把修改记录写到 redo log(日志文件)
↓
2. 再把修改写到数据文件( kc22.dbf)
关键:日志必须先落盘,数据文件可以后写。
这样如果写数据文件时断电了:
- 重启后数据库读redo log,发现有一条修改记录
- 根据日志重做(REDO)这次修改
- 数据恢复一致
这就是"redo"这个名字的来源------重做日志。记录的是"做了什么修改",崩溃后可以重新做一遍。
为什么不能没有redo
如果redo日志本身损坏了呢?
数据库启动时要检查redo log,用它来确认:上一次关闭前,哪些修改已经写到数据文件了,哪些还没写。
- 如果redo log完整 → 未写的修改可以重做 → 数据库恢复一致 → 正常打开
- 如果redo log损坏 → 不知道哪些修改已经落盘 → 数据库不敢打开 → 因为打开就是不一致的状态
这就是为什么redo坏了,整个数据库就起不来。不是因为"日志文件是个文件,坏了就报错"这么简单------而是因为redo是数据库一致性的最后保障,没有它,数据库无法确认自己的状态。
Oracle的redo结构
Oracle有三组redo日志,循环使用:
Group 1 (redo01.log) → Group 2 (redo02.log) → Group 3 (redo03.log) → 回到Group 1
写入过程:
- 用户执行DML,修改产生redo记录,先进入Log Buffer(内存中的日志缓冲区)
- 满足条件时(提交、日志缓冲区满、超时),Log Buffer的内容刷到当前redo日志文件(磁盘)
- 当前日志文件写满了,切换到下一组(log switch)
- 三组都写满了,回到第一组覆盖(如果第一组还在被检查点使用,会等)
"切换"时,数据库要确保当前日志对应的脏数据已经全部写到数据文件了。如果没写完,数据库会等------这叫日志切换等待(log switch checkpoint)。
我那次损坏的,就是其中一组日志文件。偏偏是数据库启动时需要检查的那组。
事务和redo的关系
什么是事务
事务是一组操作的逻辑单元,四个特性------ACID:
- Atomic(原子性):要么全成功,要么全回滚
- Consistent(一致性):事务前后数据状态一致
- Isolation(隔离性):事务之间互不干扰
- Durable(持久性):事务提交后,修改永久保存
redo和D(持久性)直接相关。事务提交时,Oracle保证redo记录已经写到磁盘。只要redo落盘了,数据文件哪怕还没写,也不怕------崩溃后可以重做。
事务的完整生命周期
BEGIN TRANSACTION
↓
执行 UPDATE(修改数据)
↓
1. 旧值写入 undo 段(用于回滚)
2. 修改数据缓冲区中的数据块(脏块)
3. 生成 redo 记录,写入 Log Buffer
↓
COMMIT
↓
4. Log Buffer 刷到 redo log 文件(保证持久性)
5. 返回"提交成功"给用户
↓
(稍后,后台进程 DBWn 把脏块写到数据文件)
注意:提交时只保证redo落盘,不保证数据文件落盘。
为什么敢这么干:顺序写的碾压级优势
数据库敢"数据文件晚写",底气来自一个存储层面的硬事实:顺序写比随机写快得多。
redo日志是顺序写------新记录追加到文件末尾,不需要寻找位置。数据文件是随机写------修改分散在不同数据块的不同位置,需要寻道、定位、修改。
差距有多大?
- 机械硬盘 :顺序写约100200MB/s,随机写约100200次IOPS(每次4KB的话约0.40.8MB/s)。差距**200500倍**。
- SSD :顺序写可以跑到带宽上限(37GB/s),随机写受写放大影响,差距缩小到**1050倍**。但仍然是数量级的差异。
这意味着:把10个事务对数据文件的10次随机写,合并成对日志文件的1次顺序写------性能提升几十到几百倍。
这就是group commit的原理:多个事务同时提交,不是每个事务单独刷一次日志,而是攒一批一起刷。10条redo记录追加到日志文件一次IO搞定,提交延迟大幅降低。
事务A提交 → 等一下
事务B提交 → 等一下
事务C提交 → 凑齐一批,一次性刷到日志文件(1次IO)
↓
A、B、C同时收到"提交成功"
Oracle的log file sync等待事件就是在等这一刷。如果group commit效率高,这个等待时间就很短。
WAL的双重价值
WAL不只是崩溃恢复的工具,它还是数据库高性能写入的基石:
- 可靠性价值:先写日志,崩溃后可重做------数据不丢
- 性能价值:用顺序写替代随机写,批量提交------吞吐量飞跃
如果把"每次提交必须把数据写到数据文件"作为约束,数据库的写入性能会被随机IO拖垮。WAL打破了这个约束------用顺序写的日志保证可靠性,把随机写的数据文件推迟到后台慢慢做。
这是数据库设计中最优雅的权衡之一。
代价就是:redo日志一旦损坏,那些"还没写到数据文件"的修改就丢了,而且数据库无法确认一致性。
恢复过程
没有备份,只能硬恢复。
第一步:理解数据库在卡哪
启动报错说redo损坏。我需要让Oracle跳过这个检查------以容忍不一致的状态打开数据库。
Oracle有隐藏参数可以做到这一点:
sql
alter system set "_allow_resetlogs_corruption"=true scope=spfile;
alter system set "_allow_terminal_recovery_corruption"=true scope=spfile;
两个参数必须同时设置。第一个允许在resetlogs时容忍数据文件中的不一致数据,第二个允许终端恢复时容忍损坏。
第二步:关闭数据库
shutdown immediate;
如果immediate关不了,用shutdown abort。
第三步:启动到mount状态
startup mount;
mount状态下数据库打开了控制文件,但没打开数据文件和redo日志。所以不会碰到损坏的redo。
第四步:使用备份控制文件恢复
sql
recover database using backup controlfile until cancel;
提示输入日志文件时输入cancel------因为日志文件已经损坏了,无法应用。
第五步:清理损坏的日志文件组
sql
alter database clear logfile group <group_number>;
把损坏的redo日志组清理掉。<group_number>替换为实际的组号,可以通过select * from v$log;查看。
第六步:用resetlogs方式打开数据库
sql
alter database open resetlogs;
这是关键一步。resetlogs会重置日志序列号,相当于给数据库一个新的开始------旧的redo日志全部作废,重新初始化三组日志。
第七步:切换日志
sql
alter system switch logfile;
打开后立刻切换一次日志,确保新的redo日志正常工作。
第八步:重置隐藏参数为false
sql
alter system set "_allow_resetlogs_corruption"=false scope=spfile;
alter system set "_allow_terminal_recovery_corruption"=false scope=spfile;
恢复完成后必须把参数改回false,否则数据库长期运行在容忍损坏的状态下,后续问题会被掩盖。
第九步:重启数据库
shutdown immediate;
startup;
重启确认参数生效,数据库正常运行。
完整流程
设置 _allow_resetlogs_corruption=true
↓
关闭数据库
↓
startup mount
↓
recover database using backup controlfile until cancel → cancel
↓
alter database clear logfile group <n>
↓
alter database open resetlogs
↓
alter system switch logfile
↓
设置 _allow_resetlogs_corruption=false
↓
重启数据库
恢复结果
恢复成功。但这次事故让我付出了代价------理解了什么叫"没有备份的恐惧"。
这次事故教会我的
- redo是数据库的命根子------它不是日志,是一致性的保障
- WAL是几乎所有关系数据库的基础设计------Oracle叫redo log,MySQL叫binlog+redo log,PostgreSQL叫WAL,SQL Server叫transaction log。名字不同,原理一样
- 事务提交时,redo落盘是硬保证------不是"尽量",是"必须"
- 没有备份就是赌命------这次赌赢了,下次不一定
下一篇,讲另一次数据库起不来的事故------undo回滚段损坏。这次不是WAL的问题,是MVCC的问题。
标签:#DBA #Oracle #WAL #redo日志 #事务 #ACID #数据库恢复 #日志损坏