[English]
作者:
fuyuncat
来源:
www.HelloDBA.com
通常来说,直接IO的效率比读入缓存的效率更高,因为它不需要分配内存。在11g中,Oracle有了一个新的特性————直接IO自动调节。它可以基于对象大小、Buffer Cache的大小等条件自动决定是否直接读取数据块。
下面的代码将演示它是如何起作用的。先获取几个相关参数:
SQL代码
- (11.2.0.1)
- HELLODBA.COM>select name, value from all_parameters where name in ('_very_large_object_threshold', '_small_table_threshold', '_db_block_buffers','_serial_direct_read');
- NAME VALUE
- -------------------------------------------------- ------------------------------
- _db_block_buffers 10912
- _small_table_threshold 218
- _very_large_object_threshold 500
- _serial_direct_read FALSE
然后创建一个测试表,
SQL代码
- HELLODBA.COM>create table dummy_obj as select a.* from t_objects a, t_objects b where rownum<=18000;
- Table created.
- HELLODBA.COM>exec dbms_stats.gather_table_stats('DEMO','DUMMY_OBJ');
- PL/SQL procedure successfully completed.
- HELLODBA.COM>exec dba_seg_info('DUMMY_OBJ');
- Total Blocks............................256
- Total Bytes.............................2097152
- Unused Blocks...........................46
- Unused Bytes............................376832
- Last Used Ext FileId....................5
- Last Used Ext BlockId...................20608
- Last Used Block.........................82
- Blocks under HWM........................210
- *************************************************
- The segment is analyzed
- 0% -- 25% free space blocks.............0
- 0% -- 25% free space bytes..............0
- 25% -- 50% free space blocks............0
- 25% -- 50% free space bytes.............0
- 50% -- 75% free space blocks............0
- 50% -- 75% free space bytes.............0
- 75% -- 100% free space blocks...........0
- 75% -- 100% free space bytes............0
- Unused Blocks...........................0
- Unused Bytes............................0
- Total Blocks............................198
- Total bytes.............................1622016
- PL/SQL procedure successfully completed.
- HELLODBA.COM>alter table dummy_obj move;
- Table altered.
- HELLODBA.COM>alter system flush buffer_cache;
- System altered.
这张表有210个数据块在高水位线以下。并且,我们把它们都从缓存中清除掉了(提示:移动表会导致缓存中所有数据块都失效)。然后我们对它进行全表扫描:
SQL代码
- D:\HelloDBA.COM\perl scripts>perl getsqlstats.pl
- tnsname (default is orcl):
- user name (default is demo):
- password:
- Array size (Default is 32):
- Execution plan format (Default is TYPICAL):
- SQL to be executed (without ;):
- select * from dummy_obj
- Seesion ID: 16 Process ID: 4960
- SQL Prepared. Process paused, press ENTER to continue...
- SQL is executing ...
- SQL Executed. Process Paused, press ENTER to continue...
- SQL_ID 81tza277w8r6h, child number 0
- -------------------------------------
- select * from dummy_obj
- Plan hash value: 18537087
- -------------------------------------------------------------------------------
- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
- -------------------------------------------------------------------------------
- | 0 | SELECT STATEMENT | | | | 60 (100)| |
- | 1 | TABLE ACCESS FULL| DUMMY_OBJ | 18000 | 1283K| 60 (2)| 00:00:01 |
- -------------------------------------------------------------------------------
- statistics
- -----------------------------------------
- consistent changes: 0
- consistent gets: 748
- consistent gets direct: 0
- consistent gets from cache: 748
- db block changes: 2
- db block gets: 1
- db block gets direct: 0
- db block gets from cache: 1
- physical reads: 202
- physical reads cache: 202
- physical reads direct: 0
- physical writes: 0
- physical writes direct: 0
- recursive calls: 20
- session logical reads: 749
目前并没有什么特别的,几乎和之前版本的行为差不多————数据块从磁盘读入了缓存。
现在,我们增加表的数据块数,使其大于小表临界值(_small_table_threshold):
SQL代码
- HELLODBA.COM>insert /*+append*/into dummy_obj select a.* from t_objects a, t_objects b where rownum<=18800;
- 18800 rows created.
- HELLODBA.COM>commit;
- Commit complete.
- HELLODBA.COM>exec dba_seg_info('DUMMY_OBJ');
- Total Blocks............................256
- Total Bytes.............................2097152
- Unused Blocks...........................37
- Unused Bytes............................303104
- Last Used Ext FileId....................5
- Last Used Ext BlockId...................20608
- Last Used Block.........................91
- Blocks under HWM........................219
- ...
- PL/SQL procedure successfully completed.
TRUNCATE会导致表的数据对象被重新分配。再次进行全表扫描:
SQL代码
- D:\HelloDBA.COM\perl scripts>perl getsqlstats.pl
- tnsname (default is orcl):
- user name (default is demo):
- password:
- Array size (Default is 32):
- Execution plan format (Default is TYPICAL):
- SQL to be executed (without ;):
- select * from dummy_obj
- Seesion ID: 16 Process ID: 3260
- SQL Prepared. Process paused, press ENTER to continue...
- SQL is executing ...
- SQL Executed. Process Paused, press ENTER to continue...
- statistics
- -----------------------------------------
- consistent changes: 0
- consistent gets: 780
- consistent gets direct: 770
- consistent gets from cache: 10
- db block changes: 1
- db block gets: 1
- db block gets direct: 0
- db block gets from cache: 1
- physical reads: 212
- physical reads cache: 5
- physical reads direct: 207
- physical writes: 0
- physical writes direct: 0
- recursive calls: 20
- session logical reads: 781
可以看到,直接IO自动调节起作用了。数据块(除了段头)被直接读取。但是,如果我们再次执行相同操作:
SQL代码
- D:\HelloDBA.COM\perl scripts>perl getsqlstats.pl
- tnsname (default is orcl):
- user name (default is demo):
- password:
- Array size (Default is 32):
- Execution plan format (Default is TYPICAL):
- SQL to be executed (without ;):
- select * from dummy_obj
- Seesion ID: 16 Process ID: 4104
- SQL Prepared. Process paused, press ENTER to continue...
- SQL is executing ...
- SQL Executed. Process Paused, press ENTER to continue...
- statistics
- -----------------------------------------
- consistent changes: 0
- consistent gets: 775
- consistent gets direct: 0
- consistent gets from cache: 775
- db block changes: 1
- db block gets: 0
- db block gets direct: 0
- db block gets from cache: 0
- physical reads: 207
- physical reads cache: 207
- physical reads direct: 0
- physical writes: 0
- physical writes direct: 0
- recursive calls: 0
- session logical reads: 775
发现数据块被读入缓存了。并且,类似的行为一致持续到我们将数据块数增加到了1091————大于小表临界值的5倍(_small_table_threshold*5)。
SQL代码
- HELLODBA.COM>truncate table dummy_obj;
- Table truncated.
- HELLODBA.COM>insert /*+append*/into dummy_obj select a.* from t_objects a, t_objects b where rownum<=96300;
- 96300 rows created.
- HELLODBA.COM>commit;
- Commit complete.
- HELLODBA.COM>exec dba_seg_info('DUMMY_OBJ');
- Total Blocks............................1152
- Total Bytes.............................9437184
- Unused Blocks...........................61
- Unused Bytes............................499712
- Last Used Ext FileId....................5
- Last Used Ext BlockId...................22784
- Last Used Block.........................67
- Blocks under HWM........................1091
- ...
- PL/SQL procedure successfully completed.
可以看到Oracle的行为发生了改变:
SQL代码
- D:\HelloDBA.COM\perl scripts>perl getsqlstats.pl
- tnsname (default is orcl):
- user name (default is demo):
- password:
- Array size (Default is 32):
- Execution plan format (Default is TYPICAL):
- SQL to be executed (without ;):
- select * from dummy_obj
- Seesion ID: 16 Process ID: 4056
- SQL Prepared. Process paused, press ENTER to continue...
- SQL is executing ...
- SQL Executed. Process Paused, press ENTER to continue...
- statistics
- -----------------------------------------
- consistent changes: 0
- consistent gets: 3955
- consistent gets direct: 3951
- consistent gets from cache: 4
- db block changes: 0
- db block gets: 0
- db block gets direct: 0
- db block gets from cache: 0
- physical reads: 1065
- physical reads cache: 0
- physical reads direct: 1065
- physical writes: 0
- physical writes direct: 0
- recursive calls: 0
- session logical reads: 3955
无论我们执行多少次全表扫描,数据块始终是被直接读取的。甚至于,我们将小表临界值改为一个非常大的数值(小于表的数据块数),Oracle同样始终进行直接读取:
SQL代码
- D:\HelloDBA.COM\perl scripts>perl getsqlstats.pl
- tnsname (default is orcl):
- user name (default is demo):
- password:
- Array size (Default is 32):
- Execution plan format (Default is TYPICAL):
- Pre-executed SQLs:
- alter session set "_small_table_threshold" = 1090... ok
- SQL to be executed (without ;):
- select * from dummy_obj
- Seesion ID: 142 Process ID: 5760
- SQL Prepared. Process paused, press ENTER to continue...
- SQL is executing ...
- SQL Executed. Process Paused, press ENTER to continue...
- statistics
- -----------------------------------------
- consistent changes: 0
- consistent gets: 3955
- consistent gets direct: 3951
- consistent gets from cache: 4
- db block changes: 0
- db block gets: 0
- db block gets direct: 0
- db block gets from cache: 0
- physical reads: 1065
- physical reads cache: 0
- physical reads direct: 1065
- physical writes: 0
- physical writes direct: 0
- recursive calls: 0
- session logical reads: 3955
我想,Oracle在发现它是一个“大表”时,已经在缓存中将这个对象标记为了一个“不缓存”对象:
但是,如果我们暂时将直接IO自动调节关闭,使其数据块被缓存:
SQL代码
- D:\HelloDBA.COM\perl scripts>perl getsqlstats.pl
- tnsname (default is orcl):
- user name (default is demo):
- password:
- Array size (Default is 32):
- Execution plan format (Default is TYPICAL):
- Pre-executed SQLs:
- alter session set events '10949 trace name context forever, level 1'... ok
- SQL to be executed (without ;):
- select * from dummy_obj
- Seesion ID: 141 Process ID: 5016
- SQL Prepared. Process paused, press ENTER to continue...
- SQL is executing ...
- SQL Executed. Process Paused, press ENTER to continue...
- statistics
- -----------------------------------------
- consistent changes: 0
- consistent gets: 3957
- consistent gets direct: 0
- consistent gets from cache: 3957
- db block changes: 1
- db block gets: 0
- db block gets direct: 0
- db block gets from cache: 0
- physical reads: 1065
- physical reads cache: 1065
- physical reads direct: 0
- physical writes: 0
- physical writes direct: 0
- recursive calls: 0
- session logical reads: 3957
然后在重新打开该特性:
SQL代码
- D:\HelloDBA.COM\perl scripts>perl getsqlstats.pl
- tnsname (default is orcl):
- user name (default is demo):
- password:
- Array size (Default is 32):
- Execution plan format (Default is TYPICAL):
- Pre-executed SQLs:
- alter session set events '10949 trace name context forever, level 0'... ok
- SQL to be executed (without ;):
- select * from dummy_obj
- Seesion ID: 136 Process ID: 5176
- SQL Prepared. Process paused, press ENTER to continue...
- SQL is executing ...
- SQL Executed. Process Paused, press ENTER to continue...
- statistics
- -----------------------------------------
- consistent changes: 0
- consistent gets: 3957
- consistent gets direct: 0
- consistent gets from cache: 3957
- db block changes: 1
- db block gets: 0
- db block gets direct: 0
- db block gets from cache: 0
- physical reads: 0
- physical reads cache: 0
- physical reads direct: 0
- physical writes: 0
- physical writes direct: 0
- recursive calls: 0
- session logical reads: 3957
可以发现数据块不再被直接读取,而是从缓存中读取了。
我们的测试还并未结束。现在让我们来在会话中修改特大对象临界值(_very_large_object_threshold,也是11g新引入的参数,值为百分数),使它计算出的数据块数正好小于我们的测试表的数据块数(1091/10912 = 0.0999):
SQL代码
- D:\HelloDBA.COM\perl scripts>perl getsqlstats.pl
- tnsname (default is orcl):
- user name (default is demo):
- password:
- Array size (Default is 32):
- Execution plan format (Default is TYPICAL):
- Pre-executed SQLs:
- alter session set "_very_large_object_threshold" = 9... ok
- SQL to be executed (without ;):
- select * from dummy_obj
- Seesion ID: 141 Process ID: 4256
- SQL Prepared. Process paused, press ENTER to continue...
- SQL is executing ...
- SQL Executed. Process Paused, press ENTER to continue...
- statistics
- -----------------------------------------
- consistent changes: 0
- consistent gets: 3955
- consistent gets direct: 3951
- consistent gets from cache: 4
- db block changes: 0
- db block gets: 0
- db block gets direct: 0
- db block gets from cache: 0
- physical reads: 1065
- physical reads cache: 0
- physical reads direct: 1065
- physical writes: 0
- physical writes direct: 0
- recursive calls: 0
- session logical reads: 3955
此时DUMMY_OBJ已经被Oracle视为了一张“特大表”,它的数据块被直接读取(尽管此时它的数据块已经被缓存住了)。
注意:尽管此时数据块被直接读取,但缓存中的数据块并未被清除。一旦我们把特大对象临界值改回来,Oracle又会从缓存中读取数据块。
注意(2):如果数据块数小于小表临界值的5倍,是不会被视为“特大表”的。
现在,让我们再引入串行直接读特性。同时,我们将表缩小为一个非常小的表:
SQL代码
- HELLODBA.COM>drop table dummy_obj;
- Table dropped.
- HELLODBA.COM>create table dummy_obj as select a.* from t_objects a, t_objects b where rownum<=1000;
- Table created.
- HELLODBA.COM>exec dbms_stats.gather_table_stats('DEMO','DUMMY_OBJ');
- PL/SQL procedure successfully completed.
- HELLODBA.COM>exec dbms_stats.gather_table_stats('DEMO','DUMMY_OBJ');
- PL/SQL procedure successfully completed.
- HELLODBA.COM>exec dba_seg_info('DUMMY_OBJ');
- Total Blocks............................16
- Total Bytes.............................131072
- Unused Blocks...........................2
- Unused Bytes............................16384
- Last Used Ext FileId....................5
- Last Used Ext BlockId...................20280
- Last Used Block.........................6
- Blocks under HWM........................14
- *************************************************
- The segment is analyzed
- 0% -- 25% free space blocks.............0
- 0% -- 25% free space bytes..............0
- 25% -- 50% free space blocks............0
- 25% -- 50% free space bytes.............0
- 50% -- 75% free space blocks............0
- 50% -- 75% free space bytes.............0
- 75% -- 100% free space blocks...........0
- 75% -- 100% free space bytes............0
- Unused Blocks...........................0
- Unused Bytes............................0
- Total Blocks............................11
- Total bytes.............................90112
- PL/SQL procedure successfully completed.
在会话中打开串行直接读特性,并执行全表扫描操作:
SQL代码
- D:\HelloDBA.COM\perl scripts>perl getsqlstats.pl
- tnsname (default is orcl):
- user name (default is demo):
- password:
- Array size (Default is 32):
- Execution plan format (Default is TYPICAL):
- Pre-executed SQLs:
- alter session set "_serial_direct_read" = true... ok
- SQL to be executed (without ;):
- select * from dummy_obj
- Seesion ID: 136 Process ID: 2152
- SQL Prepared. Process paused, press ENTER to continue...
- SQL is executing ...
- SQL Executed. Process Paused, press ENTER to continue...
- statistics
- -----------------------------------------
- consistent changes: 0
- consistent gets: 44
- consistent gets direct: 41
- consistent gets from cache: 3
- db block changes: 0
- db block gets: 0
- db block gets direct: 0
- db block gets from cache: 0
- physical reads: 11
- physical reads cache: 0
- physical reads direct: 11
- physical writes: 0
- physical writes direct: 0
- recursive calls: 1
- session logical reads: 44
发生了什么呢————无论表有多少个数据块,它们始终被直接读取。并且该特性的优先级比直接IO自动调节更高。
现在我们将环境恢复(关闭串行直接读),准备结束本次测试:
SQL代码
- D:\HelloDBA.COM\perl scripts>perl getsqlstats.pl
- tnsname (default is orcl):
- user name (default is demo):
- password:
- Array size (Default is 32):
- Execution plan format (Default is TYPICAL):
- Pre-executed SQLs:
- alter session set "_serial_direct_read" = false... ok
- SQL to be executed (without ;):
- select * from dummy_obj
- Seesion ID: 140 Process ID: 2792
- SQL Prepared. Process paused, press ENTER to continue...
- SQL is executing ...
- SQL Executed. Process Paused, press ENTER to continue...
- statistics
- -----------------------------------------
- consistent changes: 0
- consistent gets: 44
- consistent gets direct: 41
- consistent gets from cache: 3
- db block changes: 0
- db block gets: 0
- db block gets direct: 0
- db block gets from cache: 0
- physical reads: 11
- physical reads cache: 0
- physical reads direct: 11
- physical writes: 0
- physical writes direct: 0
- recursive calls: 0
- session logical reads: 44
嗯?这张表的数据块不能再被缓存住了。原因或许是Oracle在做串行直接读时,已经在缓存中将该表标识为了一个特别类型,使得Oracle始终对其进行直接读。
作为总结,我这里将整个过程用伪代码描述出来:
SQL代码
- Read segment header;
- Get #blocks under high water mark;
- IF (Serial Direct Read is on)
- read data blocks from disk directly;
- ELSE IF (#blocks <= Small Table Threshold) THEN
- IF (data blocks are cached) THEN
- read from cache;
- ELSE
- read data blocks from disk into cache;
- END IF
- ELSE IF (#blocks > Small Table Threshold AND #blocks <= Small Table Threshold * 5) THEN
- IF segment header is not cached THEN
- IF (data blocks are cached) THEN
- read from cache;
- ELSE
- read data blocks from disk directly
- END IF
- ELSE
- read data blocks from disk into cache
- END IF
- ELSE
- IF (data blocks are not cached AND #blocks <= Very Large Object Threshold/100 * Number of Buffer Blocks) THEN
- read from cache;
- ELSE
- read data blocks from disk directly;
- END IF
- END IF
--- Fuyuncat ---