开发转兼职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有两个用途:
- 回滚:事务没提交就断电了,重启后Oracle根据undo把数据改回旧值
- 一致性读:事务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段里,数据表里只有最新版本,不会膨胀。
这次事故教会我的
- undo不只是"回滚"------它是MVCC的基础,是并发性能的保障
- 读写不互斥是数据库的基本能力------没有MVCC,高并发场景下数据库就是个排队系统
- 数据库启动时要检查redo和undo------两个都完好才能打开
- MANUAL模式下用SYSTEM表空间当undo------这是绕过损坏的应急方案,不是长期方案
- 损坏的数据文件可以offline drop------丢了数据但至少数据库能打开
下一篇,从救火转向防火------参数配置、内存结构、性能监控、备份策略。
标签:#DBA #Oracle #MVCC #undo #回滚段 #事务隔离 #数据库恢复 #并发控制