undo 和 redo

undo 用于撤销修改的操作(rollback,事务回滚)。

redo 用于将数据的修改重演一遍(rollforward,数据前滚,数据恢复)。

undo

目的:

  • 事务的回滚
  • 实例的恢复
  • 提供查询的一致性读

回滚段能够实现数据库的读不会受到写的阻塞。

回滚段有自身无法解决的问题,例如 ORA-01555。

回滚段可以实现对历史数据的查询(flashback)。

回滚段和事务的关系:

查看事务信息:

  1. -- 插入一条数据
  2. insert into t values(1);
  3. -- 查看当前存活的事务
  4. select xid, xidUsn, xidSlot, xidSqn, ubaFil, ubaBlk, ubaSqn from v$transaction;

查询的结果:

xid xidUsn xidSlot xidSqn ubaFil ubaBlk ubaSqn
08001B0087390000 8 27 14727 3 4357 2844
事务编号
oracle中,X通常表示事务
回滚段 事务信息的槽位 序号 回滚段所在的文件 所在的数据块 数据块的地址序号

事务的xidxidUsnxidSlotxidSqn的16进制拼接而成:

to_number('08','xxxx') == 8;
to_number('001B', 'xxxx') == 27;
to_number('3987','xxxx') == 14727;

通过xid就可以定位到一个事务:用户修改一个数据块后,修改的数据块存在回滚段上,其他用户如果要用到这个回滚段,就可以通过xid找到这个回滚段。

回滚段工作流程

数据块42上有两个数据槽,存放着表 score 的数据:col0 存放字段team,col2存放字段wickets

第一条记录:

block 42 slot 0
col0: ENG
col1: 841
col2: 3

第二条记录:

block 42 slot 1
col0: AUS
col1: 77
col2: 8

发出一条SQL:

update score set wickets=9 where team='AUS';

此时oracle执行步骤为:

  1. 在 Log Buffer 新增一条记录: | 5.2 | slot 22: 10 | | —- | —- |

5.2表示对 undo header的操作。

oracle的log buffer中通过op代码记录相关操作类型。5 开头的op code表示对回滚段的操作:5.2表示对回滚段头的操作,5.1表示对回滚段数据块的操作。

slot 22:10表示要操作的槽位。

因为要对undo header操作,所以产生了一条redo。

  1. 在回滚段头上增加记录:slot 22: 10

  2. 在Log Buffer 新增一条记录: | 5.1 | block 42 slot 1 | | —- | —- | | | col2: 8 |

此时要将原始记录中的字段值记录到回滚段数据块中,所以会产生一条undo的redo。

  1. 将原始值记录到回滚段数据块中 | 5.1 | block 42 slot 1 | | —- | —- | | | col2: 8 |
  1. 在 Log Buffer新增一条记录: | 11.5 | block 42 slot 1 | | —- | —- | | | col2: 9 |

op code 为 11.x表示的是对数据行的操作。

将要变更的值记录到 redo 日志中

  1. 修改字段值为9

此时在不进行commitrollback的情况下,继续发出一条改变的 SQL:

update score set wickets=10 where team='AUS';

Oracle的执行过程同上面的update语句大体一致,但是因为是同一个回滚段,所以不再操作 undo header,也不再生成 undo header对应的redo日志。

此时如果发出 rollback 撤销上面的两条update,则oracle执行过程为:

  1. 从回滚段倒着开始读,获取到wickets字段的上次值9。

  2. 因为要操作数据块,所以此时会产生redo | 11.5 | block 42 slot 1 | | —- | —- | | | col2: 9 |

  1. 将数据块上wickets字段值改为9

  2. 此时oracle需要对 undo header 的结构进行一下维护,因为要做回滚段的头,所以会产生一条redo: | 5.6 | slot 22: 10 | | —- | —- |

  1. 维护 undo header

  2. 继续读回滚段,获取到第一次update之前的wickets的值8

  3. 同样的生成一条 redo 日志

  4. 将数据块上的wickets的值改为8

  5. 生成一条维护 undo header 的redo: | 5.11 | slot 22: 10 | | —- | —- |

  1. 维护 undo header

  2. 操作完成,生成一条 commit的 redo日志: | 5.4 | slot 22: 9 | | —- | —- |

5.4表示操作完成,不管是 commit还是 rollback 最后都会生成这样一条提交的日志,表示事务的结束。

  1. 维护 undo header的槽位
    slot 22: 9
    

回滚段的逻辑结构

回滚段是允许重用的,一旦事务提交或撤销,这个回滚段数据块就可以被其他用户使用。

回滚段的使用遵循 LRU(最近没有被使用)算法:在申请回滚段空间时,会去寻找最近最没有被使用过的回滚段数据块作为 extent。

一致性读

DML操作不再阻塞读。

  1. 用户A发出一条查询SQL
  2. A在查询的过程中,B修改了一条数据,但是没有commit
  3. A遇到B未提交的数据,从 undo段中读取查询开始时的快照,不需要等待
  4. A查询结束

一致性读并非用要去读取回滚段。

读的两种方式:

  • consistent read(一致性读)

    查看执行计划统计信息时,虽然对表没有任何修改,依然会查看select该表时出现了 consistent read一致性读。所以,不管有没有读回滚段,读的操作都叫做一致性读。

  • current read(读当前状态)

    执行update语句时,需要操作的是数据块当前的状态,所以不能进行一致性读。

此时看执行计划的统计信息时,出现的是 db block gets

即使别人也在操作该表,也不能到回滚段上去读,此时需要等待别的用户(即释放锁)才能进行update操作。

缺点:select查询时可能会出现 ORA-01555错误

ORA-01555: snapshot too old: rollback segment number string with name "string" too small

原因:从回滚段读取数据时,可能因为执行的时间过长,导致这部分回滚段上的数据已经被覆盖掉了,报出01555错误。

即使是读已提交的数据,也可能需要读取回滚段:oracle事务在提交或回滚之后,并不会直接取将对应数据块进行消除,因为这样代价太大。而是在查询访问这些数据块时,才进行 块消除 的操作,这个块消除操作也会产生回滚,所以查询已提交的数据时也可能需要访问回滚段上的数据。

自动管理UNDO—AUM

在Oracle 9i 之前,需要手工创建回滚段:

create rollback segment rbs1 tablespace xxx;

弊端:

  • 考虑回滚段的个数(系统的事务数量,并发数量)
  • 考虑回滚段的存储设置

Oracle 9i ,开始使用自动管理回滚段(Automatic Undo Management)。

Oracle 11g,开始创建 undo 表空间变成数据库创建的一部分。

优点:

  • Oracle根据业务需求,自动创建新的回滚段或者将多的回滚段离线
  • Oracle自动管理 undo 数据的保留时间(需要预先设置)

undo相关参数

查看undo相关参数:

show parameter undo;
NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------
undo_management                      string      AUTO
undo_retention                       integer     900
undo_tablespace                      string      UNDOTBS1

undo_management:管理方式,默认为AUTO自动管理,也可以改为手动管理,建议自动管理。

undo_retention:undo保留时间,单位秒。是一个动态调整的参数,同时,oracle无法保证在这个保留时间内的undo数据不被覆盖,当undo空间不足时,oracle将覆盖即使未过保留期的数据以释放空间(未提交的数据不会被覆盖)。

undo_tablespace:undo表空间,当管理方式为自动管理时,需要有一个undo表空间,oracle自动创建的回滚段会存放在这个undo表空间中。undo表空间可以创建多个,但“当前”状态的只会有一个。

强制保留 undo_retention 时间内的数据:

设置undo tablespace GUARANTEE属性

-- 即使oracle申请不到外部的undo空间,也不允许覆盖在retention时间内的undo数据
alter tablespace UNDOTBS1 retention GUARANTEE;
-- 取消GUARANTEE属性
alter tablespace UNDOTBS1 retention NOGUARANTEE;

undo相关视图

查看回滚段:

select usn,name from v$rollname;
  • usn:回滚段编号

  • name:回滚段名称

    一个SYSTEM回滚段,用于oracle系统的一些操作的回滚。

默认情况下,AUM会另外再起10个回滚段

查看回滚段信息:

select usn,extents,rsSize, shrinks,wraps,extends,status from v$rollstat;
  • usn:回滚段编号
  • extents:extent数量
  • rssize:尺寸
  • shrinks:回收的次数
  • wraps:extent的切换记录
  • extends:申请的扩展次数
  • status:状态(online/offline)

oracle 9i之后的回滚段统计信息视图:

select * from v$undostat;

保留4天的数据,每次快照10分钟。更早期的数据保存在 DBA_HIST_UNDOSTAT视图中。

  • begin_time:快照起始时间
  • end_time:快照结束时间
  • undoBlks:回滚次数
  • txnCount:事务数
  • maxQueryLen:最大一次查询所用的时间(秒)
  • maxQueryID:最大一次查询的ID(即v$sql中的sql_id
  • maxConcurrency:最大并发数
  • ssOldErrCnt:出现ORA-01555回滚段过小错误的次数(一致性读、flashback等操作可能出现该错误)
  • noSpaceErrCnt:回滚段没有扩展空间的错误出现次数
  • activeBlks:活动块的个数
  • tuned_undoRetention:调整了的undo_retention时间(如果快照这段时间内,sql执行时间比较长,oracle就会将这个时间调整的长一些,防止出现 ora-01555错误)
  • unxpStealCnt、unxpBlkRelCnt:覆盖未过期的undo块来扩展undo空间的次数。

undo tablespace

在 11g开始,创建数据库时必须创建undo表空间。最好将undo表空间设置为自动扩展,以免出现类似 ORA-01555的错误。