block cleanout分析
问题1、什么是block cleanout?
问题2、block cleanout有分哪几种情况?这些block cleanout种类又是如何运作的?
问题3、如何进行手动block cleanout?
问题1、什么是block cleanout?
首先我们先做下下面的三个实验,通过对redo size的变化情况来对block cleanout有个感性认识:
alter system set DB_CACHE_SIZE=4M;--有512个8KB大小的数据块
实验1、
SQL> drop table tt;
表已删除。
SQL>
create table tt
2
( x char(2000),
3
y char(2000),
4
z char(2000) ) ;
表已创建。
SQL> set autotrace traceonly statistics ;
SQL> insert into tt
2
select 'x', 'y', 'z'
3
from all_objects
4
where rownum <= 500;
已创建500行。
统计信息
----------------------------------------------------------
1405 recursive calls
2772 db block gets
2327 consistent gets
213 physical reads
3311036 redo size --
这里的
redo size
是对
insert
操作所做的
redo
672 bytes sent via SQL*Net to client
625 bytes received via SQL*Net from client
4 SQL*Net roundtrips to/from client
25 sorts (memory)
0 sorts (disk)
500 rows processed
//
在事务还没有提交前就将被修改的数据块写进磁盘中去
SQL>
ALTER SESSION SET EVENTS 'immediate trace name flush_cache';
会话已更改。
SQL> commit;
提交完成。
SQL> select * from tt;
已选择500行。
统计信息
----------------------------------------------------------
28 recursive calls
0 db block gets
1084 consistent gets
506 physical reads
36044 redo size --
事务递交前
modified block
就被
flush
回硬盘,此时发生
delayed --block cleanout
,所以
redo size
仍然很大
3024323 bytes sent via SQL*Net to client
748 bytes received via SQL*Net from client
35 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
500 rows processed
SQL> select * from tt;
已选择500行。
统计信息
----------------------------------------------------------
0 recursive calls
0 db block gets
507 consistent gets
0 physical reads
0 redo size --
由于进行过块清除,所以当再次进行访问这些数据块时,这些
--
被修改的数据块的块头中的事务的信息已经被清除掉,所以
redo size=0
3024323 bytes sent via SQL*Net to client
748 bytes received via SQL*Net from client
35 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
500 rows processed
实验2、
SQL> drop table tt;
SQL>
create table tt
2
( x char(2000),
3
y char(2000),
4
z char(2000) ) ;
表已创建。
SQL>
set autotrace traceonly statistics ;
SQL> insert into tt
2
select 'x', 'y', 'z'
3
from all_objects
4
where rownum <= 500;
已创建500行。
统计信息
----------------------------------------------------------
1399 recursive calls
2773 db block gets
2281 consistent gets
194 physical reads
3311368 redo size --
事务递交时对
insert
操作进行了
redo,
也进行了快速块清除,所
--
以
redo size
很大
675 bytes sent via SQL*Net to client
625 bytes received via SQL*Net from client
4 SQL*Net roundtrips to/from client
25 sorts (memory)
0 sorts (disk)
500 rows processed
SQL> commit;
提交完成。
SQL> select * from tt;
已选择500行。
统计信息
----------------------------------------------------------
28 recursive calls
0 db block gets
571 consistent gets
0 physical reads
0 redo size --
事务递交时已经进行了快速块清除,将所有的被修改的数据
--
块的事务信息都已经清除掉,所以
redo size=0
3024323 bytes sent via SQL*Net to client
748 bytes received via SQL*Net from client
35 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
500 rows processed
实验3、
SQL> drop table tt;
表已删除。
SQL>
create table tt
2
( x char(2000),
3
y char(2000),
4
z char(2000) ) ;
表已创建。
SQL> set autotrace traceonly statistics ;
SQL> insert into tt
2
select 'x', 'y', 'z'
3
from all_objects
4
where rownum <=10000;
已创建10000行。
统计信息
----------------------------------------------------------
4128 recursive calls
48003 db block gets
18138 consistent gets
2 physical reads
65458116 redo size --
事务递交时对
insert
操作进行了
redo,
也进行了快速块清除
680 bytes sent via SQL*Net to client
626 bytes received via SQL*Net from client
4 SQL*Net roundtrips to/from client
25 sorts (memory)
0 sorts (disk)
10000 rows processed
SQL> commit;
提交完成。
SQL> select * from tt;
已选择10000行。
统计信息
----------------------------------------------------------
28 recursive calls
0 db block gets
15767 consistent gets
0 physical reads
409092 redo size --
事务递交时已经进行了快速块清除,但是并没有将所有被修改的
--
数据块的事务信息清除掉,所以在第一次对该表进行查询时需要
--
将剩余的被修改的数据块上的事务信息清除掉,所以
redo size>0
60477113 bytes sent via SQL*Net to client
7711 bytes received via SQL*Net from client
668 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
10000 rows processed
SQL> select * from tt;
已选择10000行。
统计信息
----------------------------------------------------------
0 recursive calls
0 db block gets
10011 consistent gets
0 physical reads
0 redo size
60477113 bytes sent via SQL*Net to client
7711 bytes received via SQL*Net from client
668 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
10000 rows processed
从上面的三个实验中可以看出redo size的变化,redo size的变化中除了DML操作所产生的redo所产生的redo size变化,还有来源于块清除(block cleanout)操作影响redo size的变化。
问题1、什么是block cleanout?
块清除(block cleanout):即删除被修改的数据块块头上的事务和数据锁定信息。
数据块块头中的事务和数据锁定信息如下部分:
Block header dump: 0x0080000a
Object id on Block? Y
seg/obj: 0xd415
csc: 0x00.1ea33c itc: 3 flg: - typ: 1 - DATA
fsl: 0 fnx: 0x0 ver: 0x01
Itl
Xid Uba Flag Lck Scn/Fsc
0x01
0xffff.000.00000000 0x00000000.0000.00 C--- 0 scn 0x0000.001ea33c
0x02 0x0002.008.00000489 0x01c0001c.01ca.1f --U- 1 fsc 0x0000.001ea453
0x03
0x0000.000.00000000 0x00000000.0000.00 ---- 0 fsc 0x0000.00000000
。。。。
tl: 207 fb: --H-FL--
lb: 0x2 cc: 2
解释下相关的标识表示的信息:
CSC
:
即
Cleanout SCN
,它是在
insert
操作事务中产生的。
Flag
:
事务标志位。各个标志的含义分别是:
C--- = transaction has been committed and locks cleaned out
-B-- = this undo record contains the undo for this ITL entry
--U- = transaction committed (maybe long ago); SCN is an upper bound
---T = transaction was still active at block cleanout SCN
---T = transaction was still active at block cleanout SCN
事务标志是
----
,这是为什么呢?一般是由于在
commit
之前进行了
buffer cache flush
,也就是说,
oracle
进程在改写数据块时,该事务还未提交,也未回滚,所以标志为空。而假如将
buffer cache flush
放在
commit
之后,该标致就为
--U-
,即事务已经提交,但是相应的锁并没有清除
(
下面的例子可以验证
)
。所以,看到后面的
Lck
位(行级锁数目)为
1
(因为我们修改了
1
条记录)。
Lb
:
每条记录中的行级锁对应
Itl
条目
lb
:
都是
0x02
。即
Itl
中的第二条。
问题2、block cleanout有分哪几种情况?这些block cleanout种类又是如何运作的?
block cleanout(块清除)分2种:
1、 快速提交清除(fast commit cleanout):
每个事务中被修改的数据块中有10%DB_CACHE_SIZE的大小是在事务提交的时候进行了块清除,这种块清除活动叫快速提交清除;
快速提交所做的操作:当一个事务访问一个数据块时,它会占用数据块Itl中的一个条目,记录下事务ID(Xid)、该事务对该数据块操作时使用的回滚块地址(Uba)和在该数据块上产生的行级锁的数量,并在对应数据行上打上行锁标志,与Itl对应。当提交时,设置Itl中的事物标志为U,并写入一个快速提交SCN(Fsc),但并不清除锁标志。
2、 延迟块清除(delayed block cleanout):
1) 如果一个事务那些超过10%的DB_CACHE_SIZE大小的部分将被留到下一次对这些数据块访问时完成;
2) 还有一种情况是当事务还没有commit提交时,被修改的数据块就已经被写入磁盘中去,然后当发生commit时,oracle不会将被修改的数据块重新读入磁盘也不会做块清除工作,而是留到下次对这些数据块访问时才做块清除工作;
下面分别对
block cleanout
的两种分类三种情况进行测试证明,对oralce做块清除时数据块的状态做跟踪分析:
搭建测试环境如下:
SQL> drop table test;
drop table test
*
第 1 行出现错误:
ORA-00942: 表或视图不存在
SQL> create table test
2
(id int,
3
name char(600))
4 pctfree 99 pctused 1;
表已创建。
SQL> alter system set DB_CACHE_SIZE=32M;
系统已更改。
SQL> drop table test;
表已删除。
SQL> create table test
2
(id int,
3
name char(600))
4 pctfree 99 pctused 1;
表已创建。
SQL> insert into test
2 select object_id,object_name
3 from all_objects
4 where rownum < 1000;
已创建999行。
统计信息
----------------------------------------------------------
3128 recursive calls
5151 db block gets
4881 consistent gets
8 physical reads
1170744 redo size
680 bytes sent via SQL*Net to client
621 bytes received via SQL*Net from client
4 SQL*Net roundtrips to/from client
55 sorts (memory)
0 sorts (disk)
999 rows processed
SQL> select rownum, dbms_rowid.rowid_relative_fno(rowid) "file#",dbms_rowid.rowid_block_number(rowid) "block#"
2
from test
3 ;
ROWNUM file# block#
---------- ---------- ----------
1 2 994
2 2 995
3 2 996
4 2 997
5 2 998
6 2 999
7 2 1000
8 2 1001
9 2 1002
10 2 1003
11 2 1004
12 2 1005
13 2 1006
14 2 1007
15 2 1008
16 2 1009
17 2 1010
18 2 1011
19 2 1012
20 2 1013
.....
ROWNUM file# block#
---------- ---------- ----------
987 2 868
988 2 869
989 2 870
990 2 871
991 2 872
992 2 873
993 2 874
994 2 875
995 2 876
996 2 877
997 2 878
998 2 879
999 2 880
999 rows selected
SQL> select max(dbms_rowid.rowid_relative_fno(rowid)) "maxfile#",
2
max(dbms_rowid.rowid_block_number(rowid)) "maxblock#",
3
min(dbms_rowid.rowid_relative_fno(rowid)) "minfile#",
4
min(dbms_rowid.rowid_block_number(rowid)) "minblock#"
5
from test ;
maxfile# maxblock#
minfile# minblock#
---------- ---------- ---------- ----------
2 1552 2 9
注:从中可以看出每行占用一个数据块,999行对应999个数据块。(这是因为设计表时是PCTUSED参数是1,也就是说一个数据块被用了1%的大小就不能继续再进行插入操作,所以下一行就插入到下一个数据块中去)。
SQL> commit;
提交完成。
下面将test表中block#=9和block#=880作为10%DB_CACHE_SIZE大小的代表,block#=9代表10%DB_CACHE_SIZE以下的数据块,block#=880代表10%DB_CACHE_SIZE以上的数据块;(因为DB_CACHE_SIZE=32M,所以10%DB_CACHE_SIZE的大小为3.2M)
情况
1:fast commit cleanout,事务修改的数据块的总的大小小于DB_CACHE_SIZE的10%情况,此时在更新提交的同时就已经进行快速块清除,即进行了fast commit cleanout。
SQL> update test set id=id+1 where rownum < 2;
--
只更新一行
已更新 1 行。
SQL> commit;
提交完成。
SQL>
alter system dump datafile 2 block 9;
系统已更改。
Block header dump: 0x00800009
Object id on Block? Y
seg/obj: 0xd41e csc: 0x00.1f86a0 itc: 2 flg: - typ: 1 - DATA
fsl: 0 fnx: 0x0 ver: 0x01
Itl
Xid Uba Flag Lck Scn/Fsc
0x01
0x0001.005.000003b8 0x01c00022.01a2.14 --U- 1 fsc 0x0000.001f8764
0x02
0x0000.000.00000000 0x00000000.0000.00 ---- 0 fsc 0x0000.00000000
tl: 610 fb: --H-FL--
lb: 0x1 cc: 2
可以看到:事务在Itl中记录下了Xid、Uba、Flag(U)、锁、Fsc;
并且记录上的锁标志以及
lb
没有清除。
情况
2:事务修改的数据块的总的大小超过了10%DB_CACHE_SIZE大小的情况,此时进行的是delayed block cleanout中的第一种情况:
SQL> update test set id=id+1; --更新999个数据块
已更新999行。
SQL> alter system dump datafile 2 block 9;
提交事务之前:
Block header dump: 0x00800009
Object id on Block? Y
seg/obj: 0xd41e csc: 0x00.1f892c itc: 2 flg: - typ: 1 - DATA
fsl: 0 fnx: 0x0 ver: 0x01
Itl
Xid Uba Flag Lck Scn/Fsc
0x01
0x0001.005.000003b8 0x01c00022.01a2.14 C--- 0 scn 0x0000.001f8764
0x02
0x0006.017.00000499 0x01c0026f.0240.49 ---- 1 fsc 0x0000.00000000
tl: 610 fb: --H-FL--
lb: 0x2 cc: 2
分析:此时你会发现之前Itl=0x01的事务的Flag(U)、锁又原来的
--U- 1变为
C--- 0
,说明快速提交清除中如果快速提交后,脏数据回写到磁盘上,此时数据块的锁标志还没有清除,后面访问该数据块的事务(只是产生数据更新的事务,不包括读事务),会先清除(Cleanout)上一个快速提交事务留下的锁标志(同时也是做锁判断),然后开始事务操作。
并且从Itl=0x02的事务可以看到,Flag和scn没有被记录,而Xid、Uba和锁标志都已经被记录。这里还可以推断出:
Xid
和
Uba
在事务开始,获取到空闲
Itl
后是最先被记录的;锁标志是针对记录一一被记录的;
Flag
和
Scn
是事务提交后被记录的。
SQL>
alter system dump datafile 2 block 880;
Block header dump: 0x00800370
Object id on Block? Y
seg/obj: 0xd41e csc: 0x00.1f8c95 itc: 2 flg: O typ: 1 - DATA
fsl: 0 fnx: 0x800371 ver: 0x01
Itl
Xid Uba Flag Lck Scn/Fsc
0x01
0x0001.005.000003b8 0x01c00028.01a2.69 C--- 0 scn 0x0000.001f8764
0x02
0x0006.017.00000499 0x01c00f7c.0242.16 ---- 1 fsc 0x0000.00000000
tl: 610 fb: --H-FL-- lb: 0x2 cc: 2
SQL> commit;
提交完成。
此时应该小于10%DB_CACHE_SIZE大小的数据块应该已经进行过快速提交块清除,可以通过查看block#=9的数据块的块头信息:
SQL> alter system dump datafile 2 block 9;
系统已更改。
Block header dump: 0x00800009
Object id on Block? Y
seg/obj: 0xd41c csc: 0x00.1f5ea1 itc: 2 flg: - typ: 1 - DATA
fsl: 0 fnx: 0x0 ver: 0x01
Itl
Xid Uba Flag Lck Scn/Fsc
0x01
0x0007.029.000003b3 0x01c00129.0224.5b C--- 0 scn 0x0000.001f56f5
0x02
0x0005.010.00000490 0x01c000e4.01c1.12 --U- 1 fsc 0x0000.001f62b6
tl: 610 fb: --H-FL--
lb: 0x2 cc: 2
从中可以看出小于10%DB_CACHE_SIZE大小的数据块在事务提交时自动进行了块清除。
但是出忽我做测试期待的结果,
大于
10%DB_CACHE_SIZE
大小的数据块也自动进行块清除,所有的被修改的数据块似乎都进行了自动进行块清除,从下面可以看出:
SQL>
alter system dump datafile 2 block 880;
系统已更改。
Block header dump: 0x00800370
Object id on Block? Y
seg/obj: 0xd41e csc: 0x00.1f8c95 itc: 2 flg: O typ: 1 - DATA
fsl: 0 fnx: 0x800371 ver: 0x01
Itl Xid Uba Flag Lck Scn/Fsc
0x01 0x0001.005.000003b8 0x01c00028.01a2.69 C--- 0 scn 0x0000.001f8764
0x02 0x0006.017.00000499 0x01c00f7c.0242.16 --U- 1 fsc 0x0000.001f8cdb
tl: 610 fb: --H-FL-- lb: 0x2 cc: 2
再次访问数据块发现下面的现象:
SQL> set autotrace traceonly statistics ;
SQL> select count(*) from test;
统计信息
----------------------------------------------------------
4 recursive calls
0 db block gets
1075 consistent gets
0 physical reads
0 redo size --
在第一次访问这些数据块也并没有进行
redo
操作,与最初的实验
—3
似乎相矛盾
409 bytes sent via SQL*Net to client
385 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1 rows processed
SQL> alter system dump datafile 2 block 9;
系统已更改。
Block header dump: 0x00800009
Object id on Block? Y
seg/obj: 0xd41e csc: 0x00.1f892c itc: 2 flg: - typ: 1 - DATA
fsl: 0 fnx: 0x0 ver: 0x01
Itl
Xid Uba Flag Lck Scn/Fsc
0x01
0x0001.005.000003b8 0x01c00022.01a2.14 C--- 0 scn 0x0000.001f8764
0x02
0x0006.017.00000499 0x01c0026f.0240.49 --U- 1 fsc 0x0000.001f8cdb
tl: 610 fb: --H-FL-- lb: 0x2 cc: 2
SQL> alter system dump datafile 2 block 880;
系统已更改。
Block header dump: 0x00800370
Object id on Block? Y
seg/obj: 0xd41e csc: 0x00.1f8c95 itc: 2 flg: O typ: 1 - DATA
fsl: 0 fnx: 0x800371 ver: 0x01
Itl
Xid Uba Flag Lck Scn/Fsc
0x01
0x0001.005.000003b8 0x01c00028.01a2.69 C--- 0 scn 0x0000.001f8764
0x02
0x0006.017.00000499 0x01c00f7c.0242.16 --U- 1 fsc 0x0000.001f8cdb
tl: 610 fb: --H-FL-- lb: 0x2 cc: 2
我发现与我最初开始的三个实验中的实验3的结果相矛盾,我怀疑是由于表中的数据记录少的原因,于是我开始创建一个比较大的表,测试如下:
SQL> drop table test;
表已删除。
SQL> create table test
2
(id int,
3
name char(600))
4 pctfree 99 pctused 1;
表已创建。
SQL> insert into test
2 select object_id,object_name
3 from all_objects
4 where rownum < 10000;
已创建9999行。
统计信息
----------------------------------------------------------
2660 recursive calls
45600 db block gets
28203 consistent gets
0 physical reads
11026032 redo size
675 bytes sent via SQL*Net to client
622 bytes received via SQL*Net from client
4 SQL*Net roundtrips to/from client
5 sorts (memory)
0 sorts (disk)
9999 rows processed
SQL> commit;
SQL> update test set id=id+1;
已更新9999行。
SQL>
alter system dump datafile 2 block 12682;
(代表
10% DB_CACHE_SIZE
范围以内的数据块)
系统已更改。
还没有提交之前如下:
Itl
Xid Uba Flag Lck Scn/Fsc
0x01
0x0002.013.0000049c 0x01c0013a.01e1.0c C--- 0 scn 0x0000.001fca7b
0x02
0x0009.02f.000004cc 0x01c00f35.01f0.3c ---- 1 fsc 0x0000.00000000
SQL>
alter system dump datafile 2 block 21784;
(代表
10% DB_CACHE_SIZE
范围以外的数据块)
系统已更改。
Itl
Xid Uba Flag Lck Scn/Fsc
0x01
0x0002.013.0000049c 0x01c0014b.01ec.44 C--- 0 scn 0x0000.001fca7b
0x02
0x0009.02f.000004cc 0x01c001fa.01f4.22 ---- 1 fsc 0x0000.00000000
SQL> commit;
提交完成。
SQL> alter system dump datafile 2 block 12682;
系统已更改。
Itl
Xid Uba Flag Lck Scn/Fsc
0x01
0x0002.013.0000049c 0x01c0013a.01e1.0c C--- 0 scn 0x0000.001fca7b
0x02
0x0009.02f.000004cc 0x01c00f35.01f0.3c --U- 1 fsc 0x0000.002047aa
SQL> alter system dump datafile 2 block 21784;
系统已更改。
Itl
Xid Uba Flag Lck Scn/Fsc
0x01
0x0002.013.0000049c 0x01c0014b.01ec.44 C--- 0 scn 0x0000.001fca7b
0x02
0x0009.02f.000004cc 0x01c001fa.01f4.22 ---- 1 fsc 0x0000.00000000
分析:此时出现了我想要的效果,在事务提交之后确实还有一部分被修改的数据块的块头的事务和锁信息没有被清除,理论上说是
10%DB_CACHE_SIZE
大小,这个可能不是很准确。
再次访问数据块,使数据块做
cleanout
SQL> select count(*) from test;
统计信息
----------------------------------------------------------
4 recursive calls
0 db block gets
15759 consistent gets
0 physical reads
408932 redo size --
第一次访问数据块进行了延迟块清除,将剩下的被修改的数据块
--
中的事务和锁信息清除,这个结果是和最初的实验
3
是相符的,
--
不矛盾,得证。
409 bytes sent via SQL*Net to client
385 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)111
0 sorts (disk)
1 rows processed
小于10%buffer的数据块没有变化
SQL> alter system dump datafile 2 block 12682;
系统已更改。
Itl
Xid Uba Flag Lck Scn/Fsc
0x01
0x0002.013.0000049c 0x01c0013a.01e1.0c C--- 0 scn 0x0000.001fca7b
0x02
0x0009.02f.000004cc 0x01c00f35.01f0.3c --U- 1 fsc 0x0000.002047aa
tl: 609 fb: --H-FL-- lb:
0x2 cc: 2 --
记录上的锁标志以及
lb
还是没有被清除,没有变化
超过10%buffer数据块 做完delay block cleanout
SQL> alter system dump datafile 2 block 21784;
系统已更改。
Itl
Xid Uba Flag Lck Scn/Fsc
0x01
0x0002.013.0000049c 0x01c0014b.01ec.44 C--- 0 scn 0x0000.001fca7b
0x02
0x0009.02f.000004cc 0x01c001fa.01f4.22 C--- 0 scn 0x0000.002047aa--
tl: 611 fb: --H-FL--
lb: 0x0 cc: 2 --
记录上的锁标志以及
lb
还是被清除,发生了变化,这里
--
的变化解释了最初实验
3
中第一次查询为什么会有
redo –size>0
原因,这里做了延迟块清除操作。
情况
3:当事务还没有commit提交时,被修改的数据块就已经被写入磁盘中去
SQL> update test set id=id+1;
已更新999行。
--
强制清数据块
buffer
SQL> alter system flush buffer_cache;
系统已更改。
SQL> commit;
提交完成。
SQL>
alter system dump datafile 2 block 12706;
系统已更改。
Itl
Xid Uba Flag Lck Scn/Fsc
0x01
0x0007.023.000003ba 0x01c00070.022a.3e C--- 0 scn 0x0000.00208b96
0x02
0x0009.026.000004ce 0x01c0030c.01f8.14 ---- 1 fsc 0x0000.00000000
tl: 609 fb: --H-FL--
lb: 0x2 cc: 2
对数据进行访问:
SQL> exec dbms_stats.gather_table_stats(USER,'TEST');
PL/SQL 过程已成功完成。
SQL> alter system dump datafile 2 block 12706;
系统已更改。
Itl
Xid Uba Flag Lck Scn/Fsc
0x01
0x0007.023.000003ba 0x01c00070.022a.3e C--- 0 scn 0x0000.00208b96
0x02
0x0009.026.000004ce 0x01c0030c.01f8.14 C--- 0 scn 0x0000.002091aa
tl: 609 fb: --H-FL--
lb: 0x0 cc: 2
问题3、如何进行手动block cleanout?
方法一、可以采用对该被修改的表执行select查询语句的方法;
方法二、采用dbms_stats.gather_table_stats函数对表进行下统计可以帮助做块清除工作;