开发转兼职DBA(四):又起不来了——MVCC、undo与回滚段

开发转兼职DBA(四):又起不来了------MVCC、undo与回滚段

某资产管理系统Oracle数据库,undo回滚段损坏,同时有其他无关表空间也损坏了。数据库起不来。这次不是redo的问题------是undo。要理解为什么undo坏了也起不来,得先理解MVCC。


文章目录

事故

又一起数据库起不来的事故。这次报错不一样:

复制代码
ORA-01595: error freeing extent (size) of rollback segment
ORA-01650: unable to extend rollback segment

undo回滚段损坏。同时还有其他无关的表空间也损坏了。

数据库无法正常启动。

为什么undo坏了也起不来

上一篇讲了redo------保证已提交的修改不丢。这篇讲undo------保证未提交的修改能回滚,以及读取时能看到一致的数据。

undo是什么

undo(回滚段)记录的是修改前的数据

复制代码
用户执行 UPDATE kc22 SET xm='李四' WHERE id=1
    ↓
1. 把旧值 '张三' 写入 undo 段
2. 把新值 '李四' 写入数据缓冲区
3. 生成 redo 记录(记录undo的修改和数据块的修改)

undo有两个用途:

  1. 回滚:事务没提交就断电了,重启后Oracle根据undo把数据改回旧值
  2. 一致性读:事务A在改数据,事务B在读同一行。B不应该看到A没提交的修改------所以B读undo里的旧值

MVCC:多版本并发控制

第二个用途就是MVCC(Multi-Version Concurrency Control)。

假设有一条数据:

复制代码
id=1, xm='张三'

时刻T1:事务A执行UPDATE kc22 SET xm='李四' WHERE id=1,未提交。

  • 数据块里:xm='李四'(新值)
  • undo段里:xm='张三'(旧值)

时刻T2:事务B执行SELECT xm FROM kc22 WHERE id=1

  • 如果允许读未提交的数据:B看到'李四'------脏读
  • 如果等A提交后再读:B被阻塞------并发性能差

MVCC的解决方案:给每个事务一个时间戳(SCN),读数据时只看自己时间戳之前的版本。

  • 事务B的时间戳早于事务A
  • 数据块里的'李四'是A改的,B的时间戳看不到
  • B去undo段里找,找到'张三'------这是A修改前的值
  • B读到'张三',不被A阻塞

读写不互斥。 写的人写新版本,读的人读旧版本。这就是MVCC。

undo坏了会怎样

数据库启动时,需要检查所有undo段的状态------哪些事务正在进行、哪些需要回滚。

如果undo段损坏了:

  • 无法确认有哪些未完成的事务
  • 无法回滚未提交的修改
  • 无法提供一致性读
  • 数据库不敢打开

跟redo损坏的逻辑一样------不是"文件坏了所以报错",而是数据库失去了确认自身状态的能力

恢复过程

这次的思路跟redo那次不同。redo那次是"跳过损坏的日志重新开始",这次是"绕过损坏的undo表空间,用系统表空间代替"。

第一步:启动到mount状态

sql 复制代码
startup mount;

第二步:从spfile创建pfile

sql 复制代码
create pfile from spfile;

pfile是文本格式的参数文件,可以直接编辑。会生成在$ORACLE_HOME/dbs/目录下,文件名格式为init<SID>.ora

第三步:关闭数据库

sql 复制代码
shutdown immediate;

第四步:修改pfile文件

编辑$ORACLE_HOME/dbs/init<SID>.ora,修改两个参数:

复制代码
undo_management=MANUAL
undo_tablespace=SYSTEM

关键:把undo管理模式从AUTO改为MANUAL,把undo表空间从损坏的用户undo表空间改为系统表空间SYSTEM。

  • undo_management=AUTO:Oracle自动管理undo段,使用指定的undo表空间
  • undo_management=MANUAL:Oracle使用回滚段(rollback segment),可以放在任何表空间里

设为MANUAL + SYSTEM,Oracle启动时就不会去读损坏的undo表空间,而是用系统表空间里的回滚段。

第五步:用修改后的pfile启动到mount

sql 复制代码
startup mount pfile=$ORACLE_HOME/dbs/init<SID>.ora;

第六步:查看数据文件列表

sql 复制代码
select file#, name from v$datafile;

记录所有数据文件的编号和路径。因为还有其他表空间也损坏了,后面要逐个处理。

第七步:尝试打开数据库

sql 复制代码
alter database open;

如果提示某个数据文件需要恢复------那是损坏的表空间对应的文件。执行下一步。

第八步:将损坏的数据文件offline并drop

sql 复制代码
alter database datafile <文件号> offline drop;

把损坏的表空间对应的数据文件offline掉,然后重新执行:

sql 复制代码
alter database open;

如果有多个损坏的文件,逐个offline drop后再open。

恢复后要做的事

数据库打开了,但undo还在用SYSTEM表空间------这不是长久之计。

1. 创建新的undo表空间:

sql 复制代码
create undo tablespace undotbs2 datafile '/path/to/undotbs2.dbf' size 500M;

2. 修改spfile指向新的undo表空间:

sql 复制代码
alter system set undo_tablespace=undotbs2 scope=spfile;
alter system set undo_management=AUTO scope=spfile;

3. 重启数据库,确认使用新的undo表空间。

4. 删除旧的损坏的undo表空间(如果还能删的话)。

完整流程

复制代码
startup mount
    ↓
create pfile from spfile
    ↓
shutdown immediate
    ↓
修改pfile: undo_management=MANUAL, undo_tablespace=SYSTEM
    ↓
startup mount pfile=...
    ↓
select file#, name from v$datafile (记录文件号)
    ↓
alter database open
    ↓
如果报错 → alter database datafile <n> offline drop → 重新open
    ↓
创建新undo表空间,改回AUTO,重启

redo和undo的关系

两次事故放在一起看,redo和undo是配合工作的:

复制代码
事务修改一行数据:
    ↓
1. 旧值 → 写入 undo 段(用于回滚和一致性读)
2. 新值 → 写入数据缓冲区(脏块)
3. 上述两步的修改记录 → 写入 redo log(用于崩溃恢复)
    ↓
提交(COMMIT):
    ↓
4. redo log 刷盘(保证持久性)
5. 返回"提交成功"
  • redo保护的是"已提交的修改不丢"------正向保障
  • undo保护的是"未提交的修改能回滚"和"读不被写阻塞"------反向保障和并发保障

崩溃恢复的过程:

复制代码
数据库重启
    ↓
1. 前滚(REDO):从redo log重做所有已提交的修改(包括undo段的修改)
2. 回滚(UNDO):根据undo段回滚所有未提交的修改
    ↓
数据一致

所以redo和undo缺一不可。redo坏了无法前滚,undo坏了无法回滚。数据库都不敢打开。

MVCC在不同数据库的实现

MVCC不是Oracle独有的,几乎所有现代关系数据库都用:

数据库 MVCC实现方式 undo/旧版本存在哪
Oracle undo段 专门的undo表空间
MySQL (InnoDB) undo log undo log文件
PostgreSQL 多版本元组 直接在数据表里(旧版本和新版本共存)
SQL Server tempdb tempdb系统数据库

实现方式不同,但核心思想一样:写不阻塞读,读不阻塞写。每个事务看到的是自己开始时刻的一致性快照。

Oracle和MySQL把旧版本放在单独的区域(undo段/undo log),PostgreSQL更直接------旧版本就跟新版本放在同一个表里,通过事务ID判断哪个版本对当前事务可见。

这也是为什么PostgreSQL的表容易膨胀------UPDATE不是修改原数据,而是插入一条新版本,旧版本还在。需要VACUUM定期清理。而Oracle的旧版本在undo段里,数据表里只有最新版本,不会膨胀。

这次事故教会我的

  1. undo不只是"回滚"------它是MVCC的基础,是并发性能的保障
  2. 读写不互斥是数据库的基本能力------没有MVCC,高并发场景下数据库就是个排队系统
  3. 数据库启动时要检查redo和undo------两个都完好才能打开
  4. MANUAL模式下用SYSTEM表空间当undo------这是绕过损坏的应急方案,不是长期方案
  5. 损坏的数据文件可以offline drop------丢了数据但至少数据库能打开

下一篇,从救火转向防火------参数配置、内存结构、性能监控、备份策略。


标签:#DBA #Oracle #MVCC #undo #回滚段 #事务隔离 #数据库恢复 #并发控制

相关推荐
字节跳动开源2 小时前
Viking AI 搜索 CLI 正式发布:会说话,就能做搜索推荐
数据库·人工智能·开源
TechWJ3 小时前
数据库在公司内网,出差路上想查数据怎么办?
服务器·数据库·mariadb
我是一颗柠檬3 小时前
【MySQL全面教学】MySQL事务与ACID Day9(2026年)
数据库·后端·mysql
橙子圆1233 小时前
Redis知识9之集群
数据库·redis·缓存
BlackHeart12033 小时前
【SQL】Oracle中序列(Sequence)作为默认值引发的ORA-00979
数据库·sql·oracle
bug菌4 小时前
【SpringBoot 3.x 第254节】夯爆了,数据库访问性能优化实战详解!
数据库·spring boot·后端
xxl大卡4 小时前
MySQL的执行流程
数据库·mysql
chicheese4 小时前
MySQL优化实践:选错JOIN 驱动表,性能相差几十倍
数据库·mysql
無限進步D4 小时前
MySQL 单行函数
数据库·mysql
AI人工智能+电脑小能手5 小时前
【大白话说Java面试题 第77题】【Mysql篇】第7题:回表查询与全表扫描的区别?
java·开发语言·数据库·mysql·面试