开发转兼职DBA(三):数据库起不来了——WAL、事务与redo日志

开发转兼职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

写入过程:

  1. 用户执行DML,修改产生redo记录,先进入Log Buffer(内存中的日志缓冲区)
  2. 满足条件时(提交、日志缓冲区满、超时),Log Buffer的内容刷到当前redo日志文件(磁盘)
  3. 当前日志文件写满了,切换到下一组(log switch
  4. 三组都写满了,回到第一组覆盖(如果第一组还在被检查点使用,会等)

"切换"时,数据库要确保当前日志对应的脏数据已经全部写到数据文件了。如果没写完,数据库会等------这叫日志切换等待(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不只是崩溃恢复的工具,它还是数据库高性能写入的基石:

  1. 可靠性价值:先写日志,崩溃后可重做------数据不丢
  2. 性能价值:用顺序写替代随机写,批量提交------吞吐量飞跃

如果把"每次提交必须把数据写到数据文件"作为约束,数据库的写入性能会被随机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
    ↓
重启数据库

恢复结果

恢复成功。但这次事故让我付出了代价------理解了什么叫"没有备份的恐惧"。

这次事故教会我的

  1. redo是数据库的命根子------它不是日志,是一致性的保障
  2. WAL是几乎所有关系数据库的基础设计------Oracle叫redo log,MySQL叫binlog+redo log,PostgreSQL叫WAL,SQL Server叫transaction log。名字不同,原理一样
  3. 事务提交时,redo落盘是硬保证------不是"尽量",是"必须"
  4. 没有备份就是赌命------这次赌赢了,下次不一定

下一篇,讲另一次数据库起不来的事故------undo回滚段损坏。这次不是WAL的问题,是MVCC的问题。


标签:#DBA #Oracle #WAL #redo日志 #事务 #ACID #数据库恢复 #日志损坏

相关推荐
小二·7 分钟前
Redis 内存溢出(OOM)排查与恢复实战
数据库·redis·bootstrap
pqk6V6Vep8 分钟前
Redis 分布式锁进阶第一篇讲解
数据库·redis·分布式
giaz14n9X25 分钟前
Redis 分布式锁进阶第六十一篇
数据库·redis·分布式
是一个Bug30 分钟前
MongoDB:像搭积木一样存数据
数据库·mongodb
ULIi096kr1 小时前
MySQL解决Too many connections报错:连接数爆满排查、优化与永久解决方案
数据库·mysql·adb
SL-staff1 小时前
(一)数据源配置 —— JVS-Rules规则引擎 V2.5 操作说明介绍
数据库·jar·规则引擎·数据源·jvs-rules·api 接口·jvs低代码
摇滚侠2 小时前
Spring 零基础入门到进阶 基于 XML 管理 Bean 14-28
xml·数据库·spring
Metaphor6922 小时前
使用 Python 给 PDF 设置背景色或背景图
数据库·python·pdf
Gauss松鼠会2 小时前
【GaussDB】GaussDB重要通信参数汇总
服务器·网络·数据库·sql·性能优化·gaussdb·经验总结
睡不醒男孩0308233 小时前
第五篇:2026年企业级 PostgreSQL 高可用方案深度横评:Patroni vs. CLup 架构与可靠性全面对决
数据库·postgresql·架构