深入原理:Consistent Reads 与 buffer cache

黄玮(Fuyuncat)

资深Oracle DBA,个人网www.HelloDBA.com,致力于数据库底层技术的研究,其作品获得广大同行的高度评价.

编辑手记:关于数据的一致性读你了解多少呢?今天作者将会从一致性读发生的情景,脏数据块的管理及一致性读的特性等多个方面去解读其原理,邀你一起get新技能!

在一致性读(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 Least 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

(1)在“读”事务开始时,数据块已经被其他事务修改但未被提交,但在数据块被读取到之前,修改已经被提交

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

(2)“读”事务开始之前,数据块被其他事务修改,当“读”事务读取到该数据块时,修改仍未提交,发生一致性读

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

以上这两种情况可以视为同一种情况:

当数据块在某个事务中被修改了,所有开始于“修改”事务开始后、提交前的所有会读取到该数据块的“读”事务,在读取到该数据块时都会发生一致性读。

(3)“读”事务开始后、在读取数据块之前,数据块被其他事务修改且未提交,当读取到该数据块时仍未提交

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

(4)在“读”事务开始后,未访问到数据块之前,其他事务中更新了数据块且已提交,当“读”事务读取到该数据块时,也同样发生一致性读

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

由于buffer cache是有限资源,CR块不能无限制被copy,参数_db_block_max_cr_dba决定了一个数据块能copy的cr块数量(包括脏数据块),该参数默认值为6.

续上:

重复以上步骤,直到发生第五次CR:

C: SQL> select object_name from sys.t_cr where object_id=20; OBJECT_NAME -------------------------------------------------------------------------------- NB

总共产生了5个CR buffer

再次发生CR

C: SQL> select object_name from sys.t_cr where object_id=20; OBJECT_NAME -------------------------------------------------------------------------------- AAA

CR buffer数量不再增加:

当cr buffer数量已经达到了_db_block_max_cr_dba的数量限制时,会将最近最少被touch的CR释放:

再次CR:

C: SQL> select object_name from sys.t_cr where object_id=20; OBJECT_NAME -------------------------------------------------------------------------------- AAA

最早的一条被释放,增加一条新的:

-----the end

原文发布于微信公众号 - 数据和云(OraNews)

原文发表时间:2016-08-16

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏数据库

按图索骥:SQL中数据倾斜问题的处理思路与方法

数据倾斜即表中某个字段的值分布不均匀,比如有100万条记录,其中字段A中有90万都是相同的值。这种情况下,字段A作为过滤条件时,可能会引起一些性能问题。 本文通...

1829
来自专栏数据和云

举一反三-分区裁剪作用的“新”发现

作者介绍 ? 赵勇 云和恩墨北区技术工程师 专注于SQL审核和优化相关工作。曾经服务的客户涉及金融保险、电信运营商、政府、生产制造等行业。 分区裁剪的定义 分区...

26210
来自专栏乐沙弥的世界

Oracle 分区表

随着表的不断增大,对于新纪录的增加、查找、删除等(DML)的维护也更加困难。对于数据库中的超大型表,可通过把它的数据分成若干个小表,从而简化数据库的管理活动。...

602
来自专栏大数据

大数据干货系列(五)-Hive总结

Hive总结 一、本质 Hive基于一个统一的查询分析层,通过SQL语句的方式对HDFS上的数据进行查 询、统计和分析。 二、四大特点** • Hive本身不存...

2119
来自专栏Linyb极客之路

Java面试中常问的数据库方面问题

B+树是一个平衡的多叉树,从根节点到每个叶子节点的高度差值不超过1,而且同层级的节点间有指针相互链接,是有序的

813
来自专栏林欣哲

MySQL数据库开发规范知识点速查

数据库设计规范 命名规范 基本设计规范 索引设计规范 字段设计规范 SQL开发规范 操作行为规范 命名规范 对象名称使用小写字母并用下划线分割 禁止使用MySQ...

35311
来自专栏desperate633

浅谈数据库查询优化的几种思路

应尽量避免全表扫描,首先应考虑在 where 及 order by ,group by 涉及的列上建立索引

851
来自专栏Django Scrapy

数据库相关知识

mysql 是关系型数据库 开源,免费 什么是事务 事务是应用程序中一系列严密的操作,所有操作必须成功完成,否则在每个操作中所作的所有更改都会被撤消。也就...

37013
来自专栏性能与架构

MySQL Query Cache实现原理

MySQL的Query Cache实现原理实际上并不是特别复杂,简单来说就是将客户端请求的Query语句(仅限于SELECT类型的Query)通过一定的hash...

36911
来自专栏友弟技术工作室

MySQL优化思路及框架

MySQL优化框架 1. SQL语句优化 2. 索引优化 3. 数据库结构优化 4. InnoDB表优化 5. MyISAM表优化 6. Memory表优化 7...

34510

扫码关注云+社区