在 Oracle 数据库中有物理读、逻辑读、一致性读、当前模式读等诸多概念,如果不理解或混淆这些概念的话,对深入理解一些知识无疑是一个障碍,但是这些概念确实挺让让人犯晕的。下面我们总结、学习一下这方面的知识点,捋一捋他们的关系和特点。
物理读(Physical Reads)
从磁盘读取数据块到内存的操作叫物理读,当 SGA 里的高速缓存(Cache Buffer)里面不存在这些数据块时,就会产生物理读;另外,像全表扫描、磁盘排序等操作也可能产生物理读,原因也是因为 Oracle 数据库需要访问的数据块较多,而有些数据块不在内存当中,需要从磁盘读取。
逻辑读(Logical Reads)
-
概念1:逻辑读指 Oracle 从内存读到的数据块数量。一般来说, logical reads = db block gets + consistent gets
-
概念2:逻辑读指的就是从 Buffer Cache 中读取数据块。按照访问数据块的模式不同,可以分为当前模式读和一致性读。
这两个概念本质是一样的,只是措辞不一样。
Oracle 性能调优中,逻辑读是个很重要的度量值,它不仅容易收集,而且能够告诉我们许多关于数据库引擎工作量的信息。
逻辑读在 Oracle 调优中有四个好处:
- 逻辑读是受制于 CPU 能力的操作,因而,很好的反映了 CPU 的使用情况。
- 逻辑读可能导致物理读,因而,通过减少逻辑读的数量,很可能会降低 I/O 操作次数。
- 逻辑读是受制于串行的操作,既然经常要考虑多用户负载的优化,最小化逻辑读将有利于避免扩展性问题。
- 逻辑读的数量可以通过 SQL 跟踪文件和动态性能视图在 SQL 语句以及执行计划级别获得。
数据块是 Oracle 最基本的读写单位,但用户所需要的数据,并不是整个数据块,而是数据块中的行或列。
当用户发出 SQL 语句时,该语句被解析执行完毕后,就开始了数据的抓取阶段,在此阶段,服务器进程会先将行所在的数据块从数据文件中读入 buffer cache,这个过程叫做物理读。物理读每读取一个块,就算一次物理读。
当块被送进 buffer cache 后,并不能立即将块传给用户,因为用户所需要的并不是整个块,而是块中的行。从 buffer cache 的块中读取行的过程,就是逻辑读。
为了完成一次逻辑读,服务器进程先要在 hash 表中查找块所在的 buffer cache 链。找到之后,需要在这个链上加一个 cache buffer chains 闩,加闩成功之后,就在这个链中寻找指定的块,并在块上加一个 pin。并释放 cache buffer chains 闩。然后就可以访问块中的行了。服务器进程不会将块中所有满足条件的行一次取出,而是根据你的抓取命令,每次取一定数量的行。这些行取出之后,会经由 PGA 传给客户端用户。行一旦从 buffer cache 中取出,会话要释放掉在块上所加的 pin,本次逻辑读就算结束,如果还要再抓取块中剩余的行,服务器进程要再次申请获得 cache bufffer 链闩,再次在块上加 pin,这就算是另外一次逻辑读。也就是说,服务器进程每申请一次 cache buffer chains 闩,就是一次逻辑读。而每次逻辑读所读取的行的数量,可以在抓取命令中进行设置。
一致性读(Consistant Get)
Oracle 是一个多用户系统。当一个会话开始读取数据还未结束读取之前,可能会有其他会话修改了它将要读取的数据。如果会话读取到修改后的数据,就会造成数据的不一致。一致性读就是为了保证数据的一致性。在 Buffer Cache 中的数据块上都会有最后一次修改数据块时的 SCN。
如果一个事务需要修改数据块中数据,会先在回滚段中保存一份修改前数据和 SCN 的数据块,然后再更新 Buffer Cache 中的数据块的数据及其 SCN,并标识其为“脏”数据。当其他进程读取数据块时,会先比较数据块上的 SCN 和进程自己的 SCN。如果数据块上的 SCN 小于等于进程本身的 SCN,则直接读取数据块上的数据;如果数据块上的 SCN 大于进程本身的 SCN,则会从回滚段中找出修改前的数据块读取数据。通常,普通查询都是一致性读。
Oracle 数据库中,consistent gets 在判断一段 SQL 的性能时非常有用。通常来讲,比较两段 SQL 的性能好坏不是看谁的执行时间短,而是看谁的 consistent gets 小。
当前模式读(DB Block Gets)
通常情况下 db block gets 可以理解为是 DML 操作才会产生的。
当前模式读即读取数据块是当前的最新数据。任何时候在 Buffer Cache 中都只有一份当前数据块。当前读通常发生在对数据进行修改、删除操作时。这时,进程会给数据加上行级锁,并且标识数据为“脏”数据。current mode 产生 db block gets,一般在 DML 操作时产生;query mode 产生 consistent gets(一致性读),一般在查询时产生。他们两个总和一般称为逻辑读(Logical Reads)。
物理读和一致性读案例
SQL> show user;
USER 为 "SCOTT"
SQL> create table test as select * from dba_objects;
表已创建。
SQL> alter session set sql_trace=true;
会话已更改。
SQL> set autotrace traceonly;
--第一次执行
SQL> select object_type, count(1) from test group by object_type;
已选择44行。
执行计划
----------------------------------------------------------
Plan hash value: 1435881708
---------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 88246 | 947K| 349 (2)| 00:00:05 |
| 1 | HASH GROUP BY | | 88246 | 947K| 349 (2)| 00:00:05 |
| 2 | TABLE ACCESS FULL| TEST | 88246 | 947K| 346 (1)| 00:00:05 |
---------------------------------------------------------------------------
Note
-----
- dynamic sampling used for this statement (level=2)
统计信息
----------------------------------------------------------
10 recursive calls
0 db block gets
1319 consistent gets
1241 physical reads
0 redo size
1718 bytes sent via SQL*Net to client
541 bytes received via SQL*Net from client
4 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
44 rows processed
--第二次执行
SQL> select object_type, count(1) from test group by object_type;
已选择44行。
执行计划
----------------------------------------------------------
Plan hash value: 1435881708
---------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 88246 | 947K| 349 (2)| 00:00:05 |
| 1 | HASH GROUP BY | | 88246 | 947K| 349 (2)| 00:00:05 |
| 2 | TABLE ACCESS FULL| TEST | 88246 | 947K| 346 (1)| 00:00:05 |
---------------------------------------------------------------------------
Note
-----
- dynamic sampling used for this statement (level=2)
统计信息
----------------------------------------------------------
0 recursive calls
0 db block gets
1246 consistent gets
0 physical reads
0 redo size
1718 bytes sent via SQL*Net to client
541 bytes received via SQL*Net from client
4 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
44 rows processed
SQL> set autotrace off
SQL> alter session set sql_trace =false;
会话已更改。
SQL> SELECT
T.VALUE || '/' || LOWER(RTRIM(I.INSTANCE, CHR(0))) || '_ora_' || P.SPID || '.trc' TRACE_FILE_NAME
FROM (
SELECT
P.SPID
FROM V$MYSTAT M, V$SESSION S, V$PROCESS P
WHERE M.STATISTIC# = 1
AND S.SID = M.SID
AND P.ADDR = S.PADDR
) P, (
SELECT
T.INSTANCE
FROM V$THREAD T, V$PARAMETER V
WHERE V.NAME = 'thread'
AND (V.VALUE = 0 OR T.THREAD# = TO_NUMBER(V.VALUE))
) I, (
SELECT VALUE FROM V$PARAMETER WHERE NAME = 'user_dump_dest'
) T;
TRACE_FILE_NAME
-------------------------------------------------------------------------------------------------
D:\app\admin\diag\rdbms\orcl\orcl\trace/orcl_ora_9656.trc
根据执行计划可知: SQL 语句在第一次执行时,一致性读(consistent gets)为 1319, 物理读(physical reads)为 1241,当前模式读(db block gets)为 0; 再次执行同样的 SQL 语句后,物理读(physical reads)降低为 0 了,因为上一次查询,Oracle 已经将表 TEST 的所有数据块读取到 buffer cache 里面了。
先用 tkprof 工具格式化一下 trace 文件,然后再分析 out_9656.prf 文件。
D:\app\admin\diag\rdbms\orcl\orcl\trace>tkprof orcl_ora_9656.trc out_9656.prf aggregate=no;
专业术语说明:
- call:每次 SQL 语句的处理都分成三个部分 Parse:这步包括语法检查和语义检查(包括检查是否有正确的授权和所需要用到的表、列以及其他引用到的对象是否存在)、以及将 SQL 语句转换、生成执行计划等。 Execute:这步是真正的由 Oracle 来执行语句。对于 insert、update、delete 操作,这步会修改数据,对于 select 操作,这步就只是确定选择的记录。 Fetch:返回查询语句中所获得的记录,这步只有 select 语句会被执行。
- count: 这个语句被 parse、execute、fetch 的次数。
- cpu:这个语句对于所有的 parse、execute、fetch 所消耗的 cpu 的时间,以秒为单位。
- elapsed:这个语句所有消耗在 parse、execute、fetch 的总的时间。
- disk:从磁盘上的数据文件中物理读取的数据块的数量。
- query:在一致性读模式下,一致性读的数量。
- current:在 current mode 下,即当前模式读下 db blocks gets 的数量。
- rows: 所有 SQL 语句返回的记录数,但是不包括子查询中返回的记录数目。对于 select 语句,返回记录是在 fetch 这一步骤,对于 insert、update、delete 操作,返回记录则是在 execute 这一步骤。
trac 文件分析
- 第一次执行 SQL 语句
SQL ID: d6byb5gv8sgd1 Plan Hash: 1435881708
select object_type, count(1)
from
test group by object_type
call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 1 0.00 0.00 0 1 0 0
Execute 1 0.00 0.00 0 0 0 0
Fetch 4 0.04 0.16 908 1246 0 44
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 6 0.04 0.17 908 1247 0 44
Misses in library cache during parse: 1
Optimizer mode: ALL_ROWS
Parsing user id: 83
Number of plan statistics captured: 1
call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 2 0.00 0.00 0 0 0 0
Execute 3 0.00 0.00 0 0 0 0
Fetch 3 0.03 0.07 333 72 0 1
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 8 0.03 0.07 333 72 0 1
Misses in library cache during parse: 1
Misses in library cache during execute: 1
2 user SQL statements in session.
1 internal SQL statements in session.
3 SQL statements in session.
由上述结果可知: disk(物理读)= 908 + 333 = 1241;query(一致性读)= 1247 + 72 = 1319。
- 第二次执行 SQL 语句
SQL ID: d6byb5gv8sgd1 Plan Hash: 1435881708
select object_type, count(1)
from
test group by object_type
call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 1 0.00 0.00 0 0 0 0
Execute 1 0.00 0.00 0 0 0 0
Fetch 4 0.01 0.01 0 1246 0 44
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 6 0.01 0.01 0 1246 0 44
Misses in library cache during parse: 0
Optimizer mode: ALL_ROWS
Parsing user id: 83
Number of plan statistics captured: 1
Misses in library cache during parse: 0
1 user SQL statements in session.
0 internal SQL statements in session.
1 SQL statements in session.
由上述结果可知: disk(物理读)= 0;query(一致性读)= 1246。
当前模式读案例
当前模式读:用当前方式从数据库高速缓存区读取的总块数。
- 创建数据表
SQL> create table t (id number); 表已创建。
- 写入数据并查看 db block gets
SQL> set autotrace traceonly;
SQL> insert into t values(100);
已创建 1 行。
执行计划
----------------------------------------------------------
---------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------
| 0 | INSERT STATEMENT | | 1 | 100 | 1 (0)| 00:00:01 |
| 1 | LOAD TABLE CONVENTIONAL | T | | | | |
---------------------------------------------------------------------------------
统计信息
----------------------------------------------------------
1 recursive calls
8 db block gets
1 consistent gets
0 physical reads
0 redo size
837 bytes sent via SQL*Net to client
777 bytes received via SQL*Net from client
3 SQL*Net roundtrips to/from client
1 sorts (memory)
0 sorts (disk)
1 rows processed
SQL> insert into t values(1001);
已创建 1 行。
执行计划
----------------------------------------------------------
---------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------
| 0 | INSERT STATEMENT | | 1 | 100 | 1 (0)| 00:00:01 |
| 1 | LOAD TABLE CONVENTIONAL | T | | | | |
---------------------------------------------------------------------------------
统计信息
----------------------------------------------------------
1 recursive calls
1 db block gets
1 consistent gets
0 physical reads
280 redo size
837 bytes sent via SQL*Net to client
778 bytes received via SQL*Net from client
3 SQL*Net roundtrips to/from client
1 sorts (memory)
0 sorts (disk)
1 rows processed
从上述执行计划可知:第一次执行 insert 操作时,db block gets 等于 8,是因为从 Oracle 11g 开始,默认创建的表不会立即分配 segment,不会占用磁盘空间,只有当第一条数据 insert 时才会分配空间且在 Oracle 11g 中默认的 db_block_size 为 8k。
SQL> show parameter db_block_size; NAME TYPE VALUE ------------------------------------ ----------- ------------------------------ db_block_size integer 8192
第二次执行 insert 操作时,因为这次写入的内容只有一行,只发生在1个数据块上,所以 db block gets 等于 1。
一致性读的计算
一般一致性读 consistent gets ~= numrows/arraysize + blocks,确切的说是 consistent reads ~= ceil(numrows/arraysize) + Total Blocks - Unused Blocks ,需要注意这个不是绝对等于,而是约等于的关系。
--创建查看 Oracle 数据库对象空间使用情况的存储过程
SQL> conn / as sysdba
CREATE OR REPLACE PROCEDURE SHOW_SPACE(P_SEGNAME IN VARCHAR2,
P_OWNER IN VARCHAR2 DEFAULT USER,
P_TYPE IN VARCHAR2 DEFAULT 'TABLE',
P_PARTITION IN VARCHAR2 DEFAULT NULL) AS
L_FREE_BLKS NUMBER;
L_TOTAL_BLOCKS NUMBER;
L_TOTAL_BYTES NUMBER;
L_UNUSED_BLOCKS NUMBER;
L_UNUSED_BYTES NUMBER;
L_LASTUSEDEXTFILEID NUMBER;
L_LASTUSEDEXTBLOCKID NUMBER;
L_LAST_USED_BLOCK NUMBER;
L_SEGMENT_SPACE_MGMT VARCHAR2(255);
L_UNFORMATTED_BLOCKS NUMBER;
L_UNFORMATTED_BYTES NUMBER;
L_FS1_BLOCKS NUMBER;
L_FS1_BYTES NUMBER;
L_FS2_BLOCKS NUMBER;
L_FS2_BYTES NUMBER;
L_FS3_BLOCKS NUMBER;
L_FS3_BYTES NUMBER;
L_FS4_BLOCKS NUMBER;
L_FS4_BYTES NUMBER;
L_FULL_BLOCKS NUMBER;
L_FULL_BYTES NUMBER;
PROCEDURE P(P_LABEL IN VARCHAR2,
P_NUM IN NUMBER) IS
BEGIN
DBMS_OUTPUT.PUT_LINE(RPAD(P_LABEL, 40, '.') || TO_CHAR(P_NUM, '999,999,999,999'));
END;
BEGIN
EXECUTE IMMEDIATE '
select ts.segment_space_management from dba_segments seg,dba_tablespaces ts
where seg.segment_name = :p_segname and (:p_partition is null or seg.partition_name = :p_partition)
and seg.owner = :p_owner
and seg.tablespace_name=ts.tablespace_name'
INTO L_SEGMENT_SPACE_MGMT
USING P_SEGNAME, P_PARTITION, P_PARTITION, P_OWNER;
IF L_SEGMENT_SPACE_MGMT = 'AUTO' THEN
DBMS_SPACE.SPACE_USAGE(P_OWNER,
P_SEGNAME,
P_TYPE,
L_UNFORMATTED_BLOCKS,
L_UNFORMATTED_BYTES,
L_FS1_BLOCKS,
L_FS1_BYTES,
L_FS2_BLOCKS,
L_FS2_BYTES,
L_FS3_BLOCKS,
L_FS3_BYTES,
L_FS4_BLOCKS,
L_FS4_BYTES,
L_FULL_BLOCKS,
L_FULL_BYTES,
P_PARTITION);
P('Unformatted Blocks ', L_UNFORMATTED_BLOCKS);
P('FS1 Blocks (0-25) ', L_FS1_BLOCKS);
P('FS2 Blocks (25-50) ', L_FS2_BLOCKS);
P('FS3 Blocks (50-75) ', L_FS3_BLOCKS);
P('FS4 Blocks (75-100) ', L_FS4_BLOCKS);
P('Full Blocks ', L_FULL_BLOCKS);
ELSE
DBMS_SPACE.FREE_BLOCKS(SEGMENT_OWNER => P_OWNER,
SEGMENT_NAME => P_SEGNAME,
SEGMENT_TYPE => P_TYPE,
FREELIST_GROUP_ID => 0,
FREE_BLKS => L_FREE_BLKS);
END IF;
DBMS_SPACE.UNUSED_SPACE(SEGMENT_OWNER => P_OWNER,
SEGMENT_NAME => P_SEGNAME,
SEGMENT_TYPE => P_TYPE,
PARTITION_NAME => P_PARTITION,
TOTAL_BLOCKS => L_TOTAL_BLOCKS,
TOTAL_BYTES => L_TOTAL_BYTES,
UNUSED_BLOCKS => L_UNUSED_BLOCKS,
UNUSED_BYTES => L_UNUSED_BYTES,
LAST_USED_EXTENT_FILE_ID => L_LASTUSEDEXTFILEID,
LAST_USED_EXTENT_BLOCK_ID => L_LASTUSEDEXTBLOCKID,
LAST_USED_BLOCK => L_LAST_USED_BLOCK);
P('Total Blocks ', L_TOTAL_BLOCKS);
P('Total Bytes ', L_TOTAL_BYTES);
P('Total MBytes ', TRUNC(L_TOTAL_BYTES / 1024 / 1024));
P('Unused Blocks', L_UNUSED_BLOCKS);
P('Unused Bytes ', L_UNUSED_BYTES);
P('Last Used Ext FileId', L_LASTUSEDEXTFILEID);
P('Last Used Ext BlockId', L_LASTUSEDEXTBLOCKID);
P('Last Used Block', L_LAST_USED_BLOCK);
END;
SQL> grant execute on show_space to scott;
SQL> conn scott/tiger;
SQL> create or replace synonym show_space for sys.show_space;
SQL> show user;
USER 为 "SCOTT"
SQL> create table test as select * from dba_objects;
表已创建。
SQL> exec dbms_stats.gather_table_stats(user, 'TEST');
PL/SQL 过程已成功完成。
SQL> set autotrace traceonly stat;
SQL> select * from test;
已选择86839行。
统计信息
----------------------------------------------------------
0 recursive calls
0 db block gets
6952 consistent gets
0 physical reads
0 redo size
9988273 bytes sent via SQL*Net to client
64198 bytes received via SQL*Net from client
5791 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
86839 rows processed
SQL> set autotrace off;
SQL> set serveroutput on;
SQL> exec show_space('TEST',USER);
Unformatted Blocks .................... 0
FS1 Blocks (0-25) .................... 0
FS2 Blocks (25-50) .................... 0
FS3 Blocks (50-75) .................... 0
FS4 Blocks (75-100) .................... 0
Full Blocks .................... 1,241
Total Blocks ........................... 1,280
Total Bytes ........................... 10,485,760
Total MBytes ........................... 10
Unused Blocks........................... 11
Unused Bytes ........................... 90,112
Last Used Ext FileId.................... 4
Last Used Ext BlockId................... 10,496
Last Used Block......................... 117
PL/SQL 过程已成功完成。
SQL> show arraysize ;
arraysize 15
SQL> select ceil(86839/15) + 1280 - 11 from dual;
CEIL(86839/15)+1280-11
----------------------
7059
参考资料
原创文章,转载请注明出处:http://www.opcoder.cn/article/43/