假设这样一种情况,一个大查询开始FTS查询大表 ,然后另外一个session更新了表末尾的某条数据并提交,更新了N次 ,由于多次事务的DML使得该block上的相关XID和LOCK信息早已荡然无存 ,该记录行上的 XID 已经被设置为 0 了,也就是根本找不着 ITL 信息 ,那这样的情况下,oracle 是怎么找出事务的 before image ?
根据 dump 出来的数据是看不出端倪的。
简单一致性验证测试
session 1 :
SQL> create table t nologging as select rownum a,aa.* from dba_objects aa,dba_objects bb where rownu
m < 1000000;
Table created.
SQL> create index t_index on t(a) nologging;
Index created.
SQL> select max(a) from t;
MAX(A)
----------
999999
SQL> declare
2 v1 number;
3 v2 number;
4 begin
5 v1 := 0;
6 v2:= 0;
7 for c in (select * from t) loop
8 if c.a > v1 then
9 v1:=c.a;
10 end if;
11 end loop;
12 dbms_output.put_line(to_char(v1));
13 end;
14 /
这时我打开 session 2 :
session 2:
SQL> update t set a = a+1 where a = (select max(a) from t);
1 row updated.
SQL> commit;
Commit complete.
SQL> update t set a = a+1 where a = (select max(a) from t);
1 row updated.
SQL> commit;
Commit complete.
SQL> update t set a = a+1 where a = (select max(a) from t);
1 row updated.
SQL> commit;
Commit complete.
SQL> update t set a = a+1 where a = (select max(a) from t);
1 row updated.
SQL> commit;
Commit complete.
SQL> update t set a = a+1 where a = (select max(a) from t);
1 row updated.
SQL> commit;
Commit complete.
SQL> update t set a = a+1 where a = (select max(a) from t);
1 row updated.
SQL> commit;
Commit complete.
SQL> update t set a = a+1 where a = (select max(a) from t);
1 row updated.
SQL> commit;
Commit complete.
SQL> select max(a) from t;
MAX(A)
----------
1000006
SQL> update t set a = a+1 where a = (select max(a) from t);
1 row updated.
SQL> commit;
Commit complete.
SQL> update t set a = a+1 where a = (select max(a) from t);
1 row updated.
SQL> commit;
Commit complete.
SQL> update t set a = a+1 where a = (select max(a) from t);
1 row updated.
SQL> commit;
Commit complete.
SQL> update t set a = a+1 where a = (select max(a) from t);
1 row updated.
SQL> commit;
Commit complete.
SQL>
这时session 1 依然还没有执行完
一会输出结果 :
999999
PL/SQL procedure successfully completed.
如果在session 2 更新数据后再 dump 数据将会看见 dump 结果中不能提供足够的信息。那我就疑惑了,这个时候一致性是怎么来维护的,淡然 before image 应该是来自 回滚段 ,但是,oracle这个时候怎么知道这行数据跟回滚段哪部分数据相关?这个信息保存在哪里? 难道 dump 出来的内容实际上掩盖了一些内容?
也就是说即使我们读到某个 行 的数据,上面没有lock,没有XID信息,但是oracle 依然会去找回滚段中有没有相关的before image,并确认block 是干净的 ?这个代价是不是太高了?
另外一个测试,可以看出
同一个BLOCK中的数据,只要有一条被更新,即使只查询没有被更新的数据,甚至通过索引直接由 ROWID 查询数据,我们可以想象为根本就不看被更新的数据一眼,但是,依然会产生 CR block
SQL> create table t1(a number, b number);
Table created.
SQL> insert into t1 select rownum ,rownum from t where rownum < 11;
10 rows created.
SQL> create index t1_index on t1(a);
Index created.
SYS在这里做一个更新b字段
SQL> update rainy.t1 set b = 0 where a = 1;
1 row updated.
SQL> select count(*) from x$bh where state = 3;
COUNT(*)
----------
50
SQL> select * from t1 where a = 2;
A B
---------- ----------
2 2
A B
---------- ----------
2 2
Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE
1 0 TABLE ACCESS (BY INDEX ROWID) OF 'T1'
2 1 INDEX (RANGE SCAN) OF 'T1_INDEX' (NON-UNIQUE)
Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
5 consistent gets
0 physical reads
52 redo size
424 bytes sent via SQL*Net to client
503 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1 rows processed
SQL>
SQL> select * from t1 where a = 2;
A B
---------- ----------
2 2
SYS查询
SQL> select count(*) from x$bh where state = 3;
COUNT(*)
----------
51
SQL> select * from t1 where a = 2;
A B
---------- ----------
2 2
SQL>
SYS查询
SQL> select count(*) from x$bh where state = 3;
COUNT(*)
----------
52
SQL>
SQL> select * from t1 where a > 8;
A B
---------- ----------
9 9
10 10
Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE
1 0 TABLE ACCESS (BY INDEX ROWID) OF 'T1'
2 1 INDEX (RANGE SCAN) OF 'T1_INDEX' (NON-UNIQUE)
Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
6 consistent gets
0 physical reads
52 redo size
462 bytes sent via SQL*Net to client
503 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
2 rows processed
SQL>
SYS查询
SQL> select count(*) from x$bh where state = 3;
COUNT(*)
----------
54
SQL>
这证明一个过程,那就是 CR 的产生,我们原来以为是这样的流程:
查看到某行,发现行上有XID信息(XID>0),根据XID找到ITL,根据ITL找到回滚段信息…… 产生 CR BLOCK
现在看来这是错误的!
CR并不是因为查看到的数据被更改过才去产生一致读,而是只要block里面存在活动事务、或者 block SCN > 查询SCN 都会产生 CR block
若存在没有活动的事务但是还没有产生commit SCN 的(ITL 中 fsc 项为0) 则先产生 delay block cleanout
那关于 CR 到底怎么从 block 定位到 undo block ,至今还没有很明确的答案(有ITL存在很容易理解,但是没有呢?), transaction table 中也只是一个 链表头 的结构……
搞了半天,发现
ITL 中
0x1 xid: 0x0005.040.00000117 uba: 0x0080233e.01f9.11 --U- 10 fsc 0x0000.0272a3b7
XID 是当前事务的事务表信息
而 uba 该事务的回滚段的地址
忽略了这样一个事实,那就是 回滚段中记录的关于block的改变,除了数据的改变还有 ITL 的改变
所以根据当前 ITL 信息可以产生CR block,CR block 里面包含了原来的ITL信息, 这样根据before ITL 信息产生 CR block ,这个CR block 里面包含了before ITL ,这样又继续重复这个过程,直到 commit scn 都小于 查询SCN 的 block 或者不再有ITL为止,产生 CR 的过程结束,若没有找到就返回 1555 错误
当然,不一定非要是查询到的记录上有XID 信息,这依然成立,行上的 lock 主要是针对 DML 起作用
顺便补一句也许是废话的话
对于select 是以时间点为准的,对于 DML 不是以时间点为准的,也就是说current mode ,当前看见的是什么就是什么,而不管时间点的一致性和过去是什么
SQL> select count(*) from t;
COUNT(*)
----------
999999
SQL>
SQL> desc t
Name Null? Type
----------------------------------------------------- -------- ----------------------
A NUMBER
OWNER VARCHAR2(30)
OBJECT_NAME VARCHAR2(128)
SUBOBJECT_NAME VARCHAR2(30)
OBJECT_ID NUMBER
DATA_OBJECT_ID NUMBER
OBJECT_TYPE VARCHAR2(18)
CREATED DATE
LAST_DDL_TIME DATE
TIMESTAMP VARCHAR2(19)
STATUS VARCHAR2(7)
TEMPORARY VARCHAR2(1)
GENERATED VARCHAR2(1)
SECONDARY VARCHAR2(1)
SQL> select max(a) from t;
MAX(A)
----------
1000010
SQL> select rowid from t where a = 1000010;
ROWID
------------------
AAAHhXAAJAAAHinAAX
该表无索引!
打开session 1 执行
SQL> update t set owner = 'QQQQQQ' where a = 1000010 ;
立即打开session 2 执行
SQL> update t set owner = 'WWWWWW' ,a = 1000011 where rowid = 'AAAHhXAAJAAAHinAAX';
1 row updated.
SQL> commit;
Commit complete.
这是 session 1 还在执行的过程中,一会就返回结果:
SQL> update t set owner = 'QQQQQQ' where a = 1000010 ;
0 rows updated.
SQL>
这就是 query mode 和 current mode 的重大差异
query mode -------- consistent gets
current mode -------- db block gets