3.1 事务机制与undo现场


3.1 事务机制与undo现场

本节摘要:事务是数据库对外的承诺,UNDO 是承诺的物理底账:每个改动在生效前先记下"怎么撤销",回滚、一致读、ORA-01555 三件大事都从这本底账出发。本节解剖一条 UPDATE 在物理层的完整足迹,并用一次长事务回滚现场演示底账的威力与脾气。

一、事务是什么:从一个转账现场看

把 1000 元从 A 账户挪到 B 账户,应用发两条 UPDATE:A 减、B 加。如果第一条成功、第二条执行到一半系统断电,钱凭空蒸发——业务不可接受。事务(Transaction)就是防这个的:两条 UPDATE 打包成一个逻辑单元,要么全成,要么全不算。物理上一个事务从第一条 DML 开始、COMMIT 或 ROLLBACK 结束;没写提交的应用里,事务边界藏在连接池与框架的自动提交开关后面——这是排查时最常踩的暗坑:你以为没有事务的地方,往往挂着一个开了一小时没提交的事务

二、ACID 的物理兑现

四个字母背起来容易,真正要紧的是"Oracle 用什么部件兑现它"。原子性靠 UNDO:任何 DML 修改数据块之前,先把"修改前的旧值"写入 UNDO 段——回滚时按 UNDO 里的旧值逆序恢复,改了什么就还原什么。持久性靠重做日志:提交时 LGWR 把重做记录刷盘(上一章的时序图),断电后 SMON 前滚重演。隔离性靠锁加多版本:写写互斥用行锁,读写互不干扰靠 UNDO 构造旧版本快照(3.2、3.3 展开)。一致性是前三者的结果而不是独立机制:约束、触发器、外键在语句与事务边界上把关。

一条 UPDATE 的物理足迹值得默写下来:第一步,旧值写进 UNDO 段(在缓冲缓存中的 UNDO 块);第二步,数据块在缓冲缓存中被修改成新值(脏块);第三步,"UNDO 写了什么、数据块改了什么"都生成重做记录进日志缓冲;第四步,提交时 LGWR 把重做刷盘、事务表打上提交号。四个落点各司其职——数据块负责"现在是新值",UNDO 负责"能回到旧值",重做负责"断电能重演"。

三、案例:一次两小时回滚的现场

背景。 凌晨跑批,某数据修复脚本批量更新了 8000 万行账务明细,跑到一半发现条件写错,DBA 手指一抖按下 ROLLBACK。应用方催问:还要多久?能不能取消回滚直接跑正确的?

操作。 先评估回滚进度——这是能回答"还要多久"的唯一依据:

-- 查活跃事务与其 undo 用量 SELECT s.sid, s.serial#, t.used_ublk, t.used_urec, r.name AS undo_seg, t.start_time FROM v$transaction t JOIN v$session s ON s.taddr = t.addr JOIN v$rollname r ON r.usn = t.xidusn; -- 估算剩余:单位是块,取样两次算速率(v$transaction 无进度列,用差分法) SELECT used_ublk FROM v$transaction WHERE addr = :taddr; -- 回滚期间禁止的粗暴操作:直接杀会话不会取消回滚 -- SMON 会接手继续回滚,杀进程只改变执行者

结果。 两次取样差分算出速率约每秒 9000 块,UNDO 已用 62 万块、消耗比例约四成,推算剩余回滚约 40 分钟。DBA 向应用方交代了时间表,并否决了"杀会话取消回滚"的提议——杀掉后 SMON 仍会继续回滚,而且失去会话级进度可见性,纯属雪上加霜。解读。 这个现场的知识内核是:回滚的成本与事务已做的功成正比,而提交几乎是恒定成本——提交只刷重做,回滚却要把每一条 UNDO 逆序重放。所以大 DML 的最佳实践是分批提交:每 1 万行一提交,把"最坏情况成本"锁在可控范围。变式。 如果现场没有足够 UNDO 耐心等,唯一干净的出路是让回滚走完;试图强制终止 SMON 回滚、或者用非法手段改事务表,历史上没有一次不进事故通报。

四、UNDO 的第二职业:一致读与 01555

UNDO 不只服务回滚。一个查询在跑的几十分钟里,别的会话提交了几百次改动,查询凭什么读到"开始那一刻"的数据?答案是顺着数据块里的事务指针去 UNDO 翻旧版本,一路翻到早于查询开始时刻的版本为止——这就是一致读。它有个脆弱点:UNDO 是循环使用的,旧版本被新事务覆盖后,迟到的查询找不到原料,报 ORA-01555 snapshot too old。解法按优先级:给 UNDO 表空间足够空间并设合理的保留目标(UNDO_RETENTION)、把巨型报表挪出批量更新窗口、实在不行用闪回查询换一个明确的时间点。3.3 节会从语义层面再回到一致读。

五、常见问题

⚠️ 常见坑:把 UNDO 表空间设成"自动扩展 + 不设上限"。长事务会把 UNDO 撑到几百 GB,回滚时间随之失控。正确姿势是固定大小加足够保留目标,让 01555 风险显性化而不是被磁盘容量掩盖。

问题一:怎么发现应用里的隐式长事务? 一条固定巡检:查活跃事务里 start_time 距今超过十分钟的会话,核对它的模块与动作。长事务的来源高度集中——交互式工具忘提交、应用异常分支漏了提交、批处理忘了设批次边界。把这条巡检做进日报,长事务就从"回滚灾难的前奏"变成"日报里的一行提示"。

问题二:自治事务什么时候该用、什么时候是坑? 该用的场景很窄:与主事务成败无关的旁路记录——审计日志、告警记录、用法统计。它是坑的场景是"业务上想回滚一部分":有人用自治事务做"主流程失败但流水留痕"之外的偏门设计,把一致性边界搅成两套,最后没人说得清哪些记录可信。判断口诀:旁路记录用自治,业务数据不越界。

问题三: flashback query 与 undo 是什么关系? 同一本底账:闪回查询就是显式指定时刻的一致读——按你给的时间戳去 UNDO 翻旧版本。所以它的可用上限同样是 UNDO 保留期,且与 01555 共享同一笔容量预算。给报表开闪回查询前,先确认 UNDO 容量按"最长闪回窗口乘变更速率"留足,两者不是免费午餐的关系而是同一份账单。

问题四:提交频繁会不会把重做日志写爆? 提交频率影响的是重做量与日志切换节奏,不是日志容量本身——日志是循环写的。真正的代价在每次提交的落盘等待:批量导入改成每行一提交,吞吐可以掉一个数量级。反向的坑也存在:整批一提交让回滚风险失控。两个极端之间,按业务可容忍的丢失窗口与重做量定批次,就是分批提交的全部含义。

本节要点回顾

  • 事务边界在物理层:首条 DML 开、COMMIT 关;应用框架的自动提交开关常让边界偏离预期。
  • 四个落点:UNDO 记旧值、数据块存新值、重做记流水、提交号定秩序——背下这张足迹图。
  • 回滚成本与功成正比:大 DML 分批提交,把最坏情况锁在分钟级。
  • UNDO 一鱼三吃:回滚、一致读、闪回查询;01555 是"旧版本被覆盖"的信号,治本靠容量与保留策略。

事务守住了"改得对"的底线。但几百个会话同时动手时,谁先谁后、谁等谁,是下一节的秩序问题。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U