惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

量子位
Stack Overflow Blog
Stack Overflow Blog
人人都是产品经理
人人都是产品经理
The GitHub Blog
The GitHub Blog
Engineering at Meta
Engineering at Meta
Vercel News
Vercel News
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Y
Y Combinator Blog
The Cloudflare Blog
Last Week in AI
Last Week in AI
B
Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
T
Tailwind CSS Blog
V
Visual Studio Blog
博客园 - 三生石上(FineUI控件)
小众软件
小众软件
Google DeepMind News
Google DeepMind News
D
DataBreaches.Net
博客园 - 司徒正美
B
Blog RSS Feed
Microsoft Azure Blog
Microsoft Azure Blog
罗磊的独立博客
Hugging Face - Blog
Hugging Face - Blog
L
LangChain Blog

博客园 - .net学习交流

Oracle profile详解 ORACLE使用JOB定时备份数据库 干掉标记为KILLED的session Oracle直方图详解(转) Export/Import DataPump Parameter QUERY - How to Specify a Query 一个完整的RMAN备份脚本 Oracle 10g的新特性flashback IBM AIX V5.3 磁盘存储管理 跨平台表空间传输(摘自eygle《循序渐进Oracle》) ORACLE EXP/IMP的使用详解 定义可延迟(deferrable)的约束 - .net学习交流 - 博客园 RMAN命令详解 Oracle中使用可传输表空间备份数据 Oracle 中使用 fetch bulk collect into 批量效率的读取游标数据 oracle 游标属性 sql%found sql%notfound sql%rowcount 在存储过程中指定回滚段 EXP-00003: 未找到段 (11,419) 的存储定义解决方法 AIX上设置LOCK_SGA=TRUE 很容易理解的IN和EXISTS区别
一致性读(Consistent Reads)与buffer cache
.net学习交流 · 2009-06-10 · via 博客园 - .net学习交流

在一致性读(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的数量限制时会将最近最少被touchCR释放

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行。