一致性读(Consistent Reads)与buffer cache
作者: fuyuncat
来源: www.HelloDBA.com
在一致性读(Consistent Read)过程中,Oracle根据SCN从undo segment/buffer中读取脏数据块的undo数据来保证查询数据的一致性。当查询读取了CR数据块时,为了提高后续CR的性能,会将CR块copy到buffer中,后续的CR就直接读取buffer。
在分析CR buffer之前,先简单看下buffer cache是怎么管理的。
我们知道,buffer cache的主要目的就是缓存那些被访问过的数据,以提高下次对这些数据的访问性能。由于一个数据库的被访问的数据量是很庞大的,但是buffer cache资源是有限的,这就需要一个对buffer cache的管理算法,以大大提高buffer cache的利用率。这一算法就是LRU算法,其基本思想就是让那些经常被访问的数据能尽量长时间的保留在buffer中,以提高数据库的整体性能。
Oracle通过两个链表来实现这种算法的管理:LRU List和Write List(也叫Dirty List)。在LRU List中,链接的是所有空闲块(Free Buffer)、正在被使用的块(Pinned Buffer)以及所有还未被放到Write List中去的脏块(Dirty Buffer)。当一个数据块被访问到了时,就会被放到LRU链表的MRU(the Most Recently Used)端,这样,那些很少被访问到的数据块就会逐渐移动到了链表的LRU(the Most Recently Used)端。当需要访问一个数据块时,用户进程会先搜索(通过hash)LRU List,看该数据块是否已经被cache住,如果有,就直接使用(buffer hit),如果没有(Buffer Miss),服务进程会从LRU List的LRU端开始搜索空闲块,并且在搜索过程中,将找到的脏块都转移到Write List上去。如果搜索一定数量(一个内部的Threshold值)的buffer块还没有找到空闲块时,服务进程就会发信号给DBW0进程将脏数据块写入磁盘并释放。
回过来再说CR。CR是发生在多个事务对相同数据进行读写时,为了保证读进程不因为时间差(query消耗的时间)而造成数据差异,让读进程读取到与本身时间戳(SCN)相符的数据块镜像。在发生CR时,CR块也会被cache到buffer中,以提高query后续的对该数据块的一致性读的效率,此时的CR buffer块会打上相应query的SCN标志,该数据块也只能被这个query使用。那么,具体什么样的情况会发生CR呢?我们通过代码演示来分析发生CR的各种情况。
注:下面是测试表t_cr的创建脚本:
SQL> create table t_cr as select * from user_objects;
表已创建。
SQL> alter table t_cr add constraint r_cr_pk primary key (object_id);
表已更改。
注:下面查询中objd是表对象的data_object_id,不是object_id,该值可以从dba_objects中查到。
一、什么情况下发生CR
· 在“读”事务开始时,数据块已经被其他事务修改但未被提交,但在数据块被读取到之前,修改已经被提交:
时间:
T1
T2
T3
T4
A事务
Block Updated
Commit
C事务
Query begin
Read Block(Consistent Read)
B:
SQL> alter system flush buffer_cache;
系统已更改。
SQL> select objd,file#,block#,status,dirty from v$bh where objd=186467 and block#= 147146 and status !='free';
未选定行
A事务更新数据,未提交:
A:
SQL> update sys.t_cr set object_name = 'NB' where object_id=20;
已更新 1 行。
修改过的数据块被cache到了buffer中:
B:
SQL> select objd,file#,block#,status,dirty from v$bh where objd=186467 and block#= 147146 and status !='free';
OBJD FILE# BLOCK# STATUS DI
---------- ---------- ---------- -------------- --
186467 1 147146 xcur Y
C事务开始query,query时间较长,运行中且未读取到被A事务修改过的数据块:
C:
SQL> select to_char(count(1)) from user_objects, user_tables
2 union all
3 select object_name from sys.t_cr where object_id=20;
(Query运行中......)
A事务提交修改
A:
SQL> commit;
提交完成。
这时,C事务读取到该数据块,对数据块的读取是一致性读(CR),其读取的数据是修改前的数据:
C:
(Query完成)
TO_CHAR(COUNT(1))
-----------------------------------------------------------------------
159390
AAA
看到UNDO数据被copy到了buffer cache中作为CR buffer存在:
B:
SQL> select objd,file#,block#,status,dirty from v$bh where objd=186467 and block#= 147146 and status !='free';
OBJD FILE# BLOCK# STATUS DI
---------- ---------- ---------- -------------- --
186467 1 147146 xcur Y
186467 1 147146 cr N
再次读取就是修改后的数据:
C:
SQL> select object_name from sys.t_cr where object_id=20;
OBJECT_NAME
-----------------------------------------------------------
NB
· “读”事务开始之前,数据块被其他事务修改,当“读”事务读取到该数据块时,修改仍未提交,发生一致性读:
时间:
T1
T2
T3
T4
A事务
Block Updated
Commit
C事务
Query begin
Read Block(Consistent Read)
B:
SQL> alter system flush buffer_cache;
系统已更改。
A事务修改数据、未提交:
A:
SQL> update sys.t_cr set object_name = 'AAA' where object_id=20;
已更新 1 行。
C事务读取数据块,发生一致性读
C:
SQL> select object_name from sys.t_cr where object_id=20;
OBJECT_NAME
--------------------------------------------------------------
NB
CR块被cache:
B:
SQL> select objd,file#,block#,status,dirty from v$bh where objd=186467 and block#= 147146 and status !='free';
OBJD FILE# BLOCK# STATUS DI
---------- ---------- ---------- -------------- --
186467 1 147146 xcur Y
186467 1 147146 cr N
以上这两种情况可以视为同一种情况:
当数据块在某个事务中被修改了,所有开始于“修改”事务开始后、提交前的所有会读取到该数据块的“读”事务,在读取到该数据块时都会发生一致性读。
· “读”事务开始后、在读取数据块之前,数据块被其他事务修改且未提交,当读取到该数据块时仍未提交:
时间:
T1
T2
T3
T4
A事务
Block Updated
Commit
C事务
Query begin
Read Block(Consistent Read)
B:
SQL> alter system flush buffer_cache;
系统已更改。
C事务开始query,query时间较长,运行中且未读取到被即将被A事务修改的数据块:
C:
SQL> select to_char(count(1)) from user_objects, user_tables
2 union all
3 select object_name from sys.t_cr where object_id=20;
(Query运行中......)
A事务修改数据块,未提交
A:
SQL> update sys.t_cr set object_name = 'AAA' where object_id=20;
已更新 1 行。
C事务访问到了被修改过的数据块,发生一致性读,读取到修改前的数据
C:
(Query完成)
TO_CHAR(COUNT(1))
------------------------------------------------------------------------------------------
159390
BBB
CR数据块被cache在buffer中:
B:
SQL> select objd,file#,block#,status,dirty from v$bh where objd=186467 and block#= 147146 and status !='free';
OBJD FILE# BLOCK# STATUS DI
---------- ---------- ---------- -------------- --
186467 1 147146 xcur Y
186467 1 147146 cr N
· 在“读”事务开始后,未访问到数据块之前,其他事务中更新了数据块且已提交,当“读”事务读取到该数据块时,也同样发生一致性读
时间:
T1
T2
T3
T4
A事务
Block Updated
Commit
C事务
Query begin
Read Block(Consistent Read)
1、数据块已经在buffer中,直接copy CR块
B:
SQL> alter system flush buffer_cache;
系统已更改。
C事务开始query,query时间较长,运行中:
C:
SQL> select to_char(count(1)) from user_objects, user_tables
2 union all
3 select object_name from sys.t_cr where object_id=20;
(Query运行中......)
A事务更新数据且提交:
A:
SQL> update sys.t_cr set object_name = 'NB' where object_id=20;
已更新 1 行。
SQL> commit;
提交完成。
C事务查询完成,读取到修改的数据,发生一致性读,读取到修改前的数据
C:
(Query完成)
TO_CHAR(COUNT(1))
-----------------------------------------------------------------------
159390
AAA
SQL> select object_name from sys.t_cr where object_id=20;
OBJECT_NAME
-------------------------------------------------------------
NB
CR块被cache在buffer中:
B:
SQL> select objd,file#,block#,status,dirty from v$bh where objd=186467 and block#= 147146 and status !='free';
OBJD FILE# BLOCK# STATUS DI
---------- ---------- ---------- -------------- --
186467 1 147146 xcur Y
186467 1 147146 cr N
以上这两种情况都可以被视为发生一致性读的另外一种情况:
在“读”事务开始后,如果有数据块被其他事务修改(无论是否被提交),在读取到被修改的数据块时都发生一致性读。
二、发生CR时,脏数据块的处理
在分析一致性读的问题时,还有一个关联问题就是对“脏”数据块的cache管理的问题。一般情况下,“修改”事务修改过数据块后,被修改过的数据块就已经被cache在buffer,且在被DBWn进程写回文件之前还有一个脏(Dirty)标志。但当要进行一致性读的事务读取到该数据块时,还会有其他一些情况可能发生:如脏数据块已经被写入文件、脏标志被清除;数据块已经被置换出buffer,这些情况下,脏数据块又是如何处理的呢?下面对各种情况逐个分析一番。
· 当发生一致性读时,如果脏数据块不在buffer中,则从数据文件中读取回脏数据块cache到buffer中:
A:
SQL> update sys.t_cr set object_name = 'AAA' where object_id=20;
已更新 1 行。
Buffer Cache被清空:
B:
SQL> alter system flush buffer_cache;
系统已更改。
SQL> select objd,file#,block#,status,dirty from v$bh where objd=186467 and block#= 147146 and status !='free';
未选定行
发生一致性读:
C:
SQL> select object_name from sys.t_cr where object_id=20;
OBJECT_NAME
--------------------------------------------------------------------------------
NB
脏数据块被复制到了buffer中:
B:
SQL> select objd,file#,block#,status,dirty from v$bh where objd=186467 and block#= 147146 and status !='free';
OBJD FILE# BLOCK# STATUS DI
---------- ---------- ---------- -------------- --
186467 1 147146 xcur Y
186467 1 147146 cr N
已选择2行。
· 当发生一致性读时,如果脏数据块已经被写回数据文件、脏标志被清除,则会重置其脏标志:
A:
SQL> update sys.t_cr set object_name = 'AAA' where object_id=20;
已更新 1 行。
数据被写入文件、脏标志被清除
B:
SQL> alter system checkpoint;
系统已更改。
SQL> select objd,file#,block#,status,dirty from v$bh where objd=186467 and block#= 147146 and status !='free';
OBJD FILE# BLOCK# STATUS DI
---------- ---------- ---------- -------------- --
186467 1 147146 xcur N
发生一致性读:
C:
SQL> select object_name from sys.t_cr where object_id=20;
OBJECT_NAME
--------------------------------------------------------------------------------
NB
脏标志被重置:
B:
SQL> select objd,file#,block#,status,dirty from v$bh where objd=186467 and block#= 147146 and status !='free';
OBJD FILE# BLOCK# STATUS DI
---------- ---------- ---------- -------------- --
186467 1 147146 xcur Y
186467 1 147146 cr N
三、CR块是被“独享”的
我们之前提到过,CR buffer只能被产生该buffer的事务读取,而不能被其它事务“共享”,其依据就是buffer中cr_scn_bas,其记录了产生该buffer的事务的SCN,该值只存在于CR Buffer中。
SQL> select cr_scn_bas,state from sys.x$bh where obj=186467 and dbablk=147146 and state!=0;
CR_SCN_BAS STATE
---------- ----------
2949774291 3
0 1
· 当多个事务都对某个数据块发生一致性读,每个事务都会在buffer中copy一块CR块:
B:
SQL> alter system flush buffer_cache;
系统已更改。
A:
SQL> update sys.t_cr set object_name = 'AAA' where object_id=20;
已更新 1 行。
第一个事务发生CR,读取数据块:
C:
SQL> select object_name from sys.t_cr where object_id=20;
OBJECT_NAME
--------------------------------------------------------------------------------
NB
在buffer中Copy一块CR Buffer
B:
SQL> select objd,file#,block#,status,dirty from v$bh where objd=186467 and block#= 147146 and status !='free';
OBJD FILE# BLOCK# STATUS DI
---------- ---------- ---------- -------------- --
186467 1 147146 xcur Y
186467 1 147146 cr N
已选择2行。
第二个事务发生CR,读取该数据块:
C:
SQL> select object_name from sys.t_cr where object_id=20;
OBJECT_NAME
--------------------------------------------------------------------------------
NB
在buffer中再Copy一块CR Buffer
B:
SQL> select objd,file#,block#,status,dirty from v$bh where objd=186467 and block#= 147146 and status !='free';
OBJD FILE# BLOCK# STATUS DI
---------- ---------- ---------- -------------- --
186467 1 147146 cr N
186467 1 147146 cr N
186467 1 147146 xcur Y
已选择4行。
· 由于buffer cache是有限资源,CR块不能无限制被copy,参数_db_block_max_cr_dba决定了一个数据块能copy的cr块数量(包括脏数据块),该参数默认值为6.
续上:
B:
SQL> select nam.ksppinm, val.KSPPSTVL
2 from x$ksppi nam,
3 x$ksppsv val
4 where nam.indx = val.indx
5 AND nam.ksppinm = '_db_block_max_cr_dba';
KSPPINM KSPPSTVL
-------------------------- -----------------
_db_block_max_cr_dba 6
重复以上步骤,直到发生第五次CR:
C:
SQL> select object_name from sys.t_cr where object_id=20;
OBJECT_NAME
--------------------------------------------------------------------------------
NB
总共产生了5个CR buffer
B:
SQL> select objd,file#,block#,status,dirty from v$bh where objd=186467 and block#= 147146 and status !='free';
OBJD FILE# BLOCK# STATUS DI
---------- ---------- ---------- -------------- --
186467 1 147146 cr N
186467 1 147146 cr N
186467 1 147146 cr N
186467 1 147146 cr N
186467 1 147146 cr N
186467 1 147146 xcur Y
已选择6行。
再次发生CR
C:
SQL> select object_name from sys.t_cr where object_id=20;
OBJECT_NAME
--------------------------------------------------------------------------------
AAA
CR buffer数量不再增加:
B:
SQL> select objd,file#,block#,status,dirty from v$bh where objd=186467 and block#= 147146 and status !='free';
OBJD FILE# BLOCK# STATUS DI
---------- ---------- ---------- -------------- --
186467 1 147146 cr N
186467 1 147146 xcur Y
186467 1 147146 cr N
186467 1 147146 cr N
186467 1 147146 cr N
186467 1 147146 cr N
已选择6行。
· 当cr buffer数量已经达到了_db_block_max_cr_dba的数量限制时,会将最近最少被touch的CR释放:
B:
SQL> select cr_scn_bas from x$bh where obj=186467 and dbablk=147146 and state=3 order by 1;
CR_SCN_BAS
----------
2949641156
2949641158
2949641160
2949641164
2949641166
已选择5行。
再次CR:
C:
SQL> select object_name from sys.t_cr where object_id=20;
OBJECT_NAME
--------------------------------------------------------------------------------
AAA
最早的一条被释放,增加一条新的:
B:
SQL> select cr_scn_bas from x$bh where obj=186467 and dbablk=147146 and state=3 order by 1;
CR_SCN_BAS
----------
2949641158
2949641160
2949641164
2949641166
2949641434
已选择5行。