Oracle IO问题解析(二)
2012-05-17 12:14
363 查看
1.2读
1.2.1物理读
产生物理读主要有以下几种情况:
第一次读取
当数据块第一次被读取到,Oracle会先将其从磁盘上读入BufferCache中,并将他们放在LRU(LastRecentlyUsed)链表的MRU(MostRecentlyUsed)端。再次访问数据块时就可以直接从BufferCache中读取、修改了。看以下例子:
数据块被重新读入BufferCache
如果有新的数据需要被读入BufferCache中,而BufferCache又没有足够的空闲空间,Oracle就根据LRU算法将LRU链表中LRU端的数据置换出去。当这些数据被再次访问到时,需要重新从磁盘读入。
全表扫描
当发生全表扫描(FullTableScan)时,用户进程读取表的数据块,并将他们放在LRU链表的LRU端(和上面不同,不是放在MRU端)。这样做的目的是为了使全表扫描的数据尽快被移出。因为全表扫描一般发生的频率较低,并且全表扫描的数据块大部分在以后都不会被经常使用到。
而如果你希望全表扫描的数据能被cache住,使之在扫描时放在MRU端,可以通过在创建或修改表(或簇)时,指定CACHE参数。
1.2.2逻辑读
逻辑读指的就是从(或者视图从)BufferCache中读取数据块。按照访问数据块的模式不同,可以分为即时读(CurrentRead)和一致性读(ConsistentRead)。注意:逻辑IO只有逻辑读,没有逻辑写。
即时读
即时读即读取数据块当前的最新数据。任何时候在BufferCache中都只有一份当前数据块。即时读通常发生在对数据进行修改、删除操作时。这时,进程会给数据加上行级锁,并且标识数据为“脏”数据。
一致性读
Oracle是一个多用户系统。当一个会话开始读取数据还未结束读取之前,可能会有其他会话修改它将要读取的数据。如果会话读取到修改后的数据,就会造成数据的不一致。一致性读就是为了保证数据的一致性。在BufferCache中的数据块上都会有最后一次修改数据块时的SCN。如果一个事务需要修改数据块中数据,会先在回滚段中保存一份修改前数据和SCN的数据块,然后再更新BufferCache中的数据块的数据及其SCN,并标识其为“脏”数据。当其他进程读取数据块时,会先比较数据块上的SCN和自己的SCN。如果数据块上的SCN小于等于进程本身的SCN,则直接读取数据块上的数据;如果数据块上的SCN大于进程本身的SCN,则会从回滚段中找出修改前的数据块读取数据。通常,普通查询都是一致性读。
下面这个例子帮助大家理解一下一致性读:
会话1中:
会话2中:
1.2.3查找数据
在一个查询操作中,大量的读操作都产生于数据的查找过程中。减少查找过程是我们优化IO性能问题的重要目标。
下面介绍几种主要的数据查找方式。
FullTableScan
当查询条件无法命中任何索引、或者扫描索引的代价大于全表扫描代价的某一比例时(由参数optimizer_index_cost_adj设定),Oracle会采用全表扫描的方式查找数据。当发生全表扫描时,Oracle会自下向上一次读取一定数量(由参数db_file_multiblock_read_count设定)的数据块,一直读取到高水位标志(HWM,HighWaterMark)下。FullTableScan会引起dbfilescatteredread事件。
INDEXUNIQUESCAN
全表扫描查找数据的效率是非常低的。而索引能大幅提高查找效率。普通索引的数据结构是B-Tree,树的叶子节点中包含数据的ROWID,指向数据记录,同时还有指针指向前一个/后一个叶子节点。索引扫描每次读取一个数据块,索引扫描是“连续的”(Sequential)。当索引为UNIQUE索引时,每个叶子节点只会指向一条数据。如果Oracle能预知扫描结果只有0或1条记录时,会采用INDEXUNIQUESCAN。当对UniqueIndex中的所有字段进行完全匹配时,会发生INDEXUNIQUESCAN。
INDEXUNIQUESCAN的查找过程如下:
从数的根节点数据块开始查找;
查找根节点块中所有key值中大于或等于要查找的值的最小key值;
如果key值大于查找值,则继续查找这个key值之前一个key值所指向的子节点数据块;
如果key值等于查找值,则继续查找这个key值所指向的子节点数据块;
如果没有key值大于或等于查找值,则继续查找最大key值所指向的子节点数据块;
如果继续查找的节点数据块是数一个分支节点,则重复2~4步;
如果查找的节点是叶子节点数据块,则在数据块中查找等于查找值的key值;
如果找到相等的key值,则返回数据和ROWID;
如果没找到相等的key值,则说明没有符合条件的数据,返回NULL。
INDEXRANGESCAN
如果通过索引查找数据时,Oracle认为会返回数据可能会大于1,会进行INDEXRANGESCAN,例如UniqueIndex中字段不完全匹配查找时、非UniqueIndex查找时。
INDEXRANGESCAN分为闭包(有前后查找边界)和非闭包(只有一边或者没有边界)。返回数据会依据索引增序排序,多个相同值则会按照ROWID的增序排序。以下的查找条件都是闭包的:
以下查找条件非闭包:
闭包条件下的INDEXRANGESCAN的查找过程如下:
从数的根节点数据块开始查找;
查找根节点块中所有key值中大于或等于要查找的起始值的最小key值;
如果key值大于起始值,则继续查找这个key值之前一个key值所指向的子节点数据块;
如果key值等于起始值,则继续查找这个key值所指向的子节点数据块;
如果没有key值大于或等于起始值,则继续查找最大key值所指向的子节点数据块;
如果继续查找的节点数据块是数一个分支节点,则重复2~4步;
如果查找的节点是叶子节点数据块,则在数据块中大于或等于要查找的起始值的最小key值;
如果Key值小于或等于结束值,则:如果所有Key字段都符合WHERE字句中的查找条件,则返回数据和ROWID;否则继续查找当前叶子节点所指向的右边的叶子节点。
INDEXUNIQUESCAN和INDEXRANGESCAN都会引起dbfilesequentialread事件。
TABLEACCESSBYINDEXROWID
当发生索引扫描时,如果需要返回的字段都在索引上,则直接返回索引上的数据,而如果还需要返回非索引上的字段的值,Oracle则需要根据从索引上查找的ROWID到对应的数据块上取回数据,这时就是TABLEACCESSBYINDEXROWID。
INDEXFASTFULLSCAN&INDEXFULLSCAN
索引快速全扫描和全表扫描类似,一次读取db_file_multiblock_read_count个数据块来描所有索引的叶子节点。INDEXFASTFULLSCAN和其他索引扫描不同,它不会从树的根节点开始读取,而是直接扫描所有叶子节点;也不会一次读取一个数据块,而是一次读取db_file_multiblock_read_count个数据块。INDEXFASTFULLSCAN会引起dbfilescatteredread事件。
在某些情况下,如db_file_multiblock_read_count值过小、强制使用索引扫描时,会发生INDEXFULLSCAN。INDEXFULLSCAN和INDEXFASTFULLSCAN不同,它是一种索引扫描,按照B-Tree的查找法从树的根节点开始扫描,遍历整棵树,并且一次读取一个数据块。它会引起dbfilesequentialread事件。
1.2.1物理读
产生物理读主要有以下几种情况:
第一次读取
当数据块第一次被读取到,Oracle会先将其从磁盘上读入BufferCache中,并将他们放在LRU(LastRecentlyUsed)链表的MRU(MostRecentlyUsed)端。再次访问数据块时就可以直接从BufferCache中读取、修改了。看以下例子:
SQL>selectowner,index_namefromt_test3;
2856rowsselected.
ExecutionPlan
----------------------------------------------------------
Planhashvalue:2878488296
-----------------------------------------------------------------------------
|Id|Operation|Name|Rows|Bytes|Cost(%CPU)|Time|
-----------------------------------------------------------------------------
|0|SELECTSTATEMENT||2856|68544|22(0)|00:00:01|
|1|TABLEACCESSFULL|T_TEST3|2856|68544|22(0)|00:00:01|
-----------------------------------------------------------------------------
Statistics
----------------------------------------------------------
407recursivecalls
32dbblockgets
344consistentgets
89physicalreads
0redosize
103888bytessentviaSQL*Nettoclient
2475bytesreceivedviaSQL*Netfromclient
192SQL*Netroundtripsto/fromclient
9sorts(memory)
0sorts(disk)
2856rowsprocessed
SQL>selectowner,index_namefromt_test3;
2856rowsselected.
Elapsed:00:00:00.03
ExecutionPlan
----------------------------------------------------------
Planhashvalue:2878488296
-----------------------------------------------------------------------------
|Id|Operation|Name|Rows|Bytes|Cost(%CPU)|Time|
-----------------------------------------------------------------------------
|0|SELECTSTATEMENT||2856|68544|22(0)|00:00:01|
|1|TABLEACCESSFULL|T_TEST3|2856|68544|22(0)|00:00:01|
-----------------------------------------------------------------------------
Statistics
----------------------------------------------------------
0recursivecalls
0dbblockgets
276consistentgets
0physicalreads
0redosize
103888bytessentviaSQL*Nettoclient
2475bytesreceivedviaSQL*Netfromclient
192SQL*Netroundtripsto/fromclient
0sorts(memory)
0sorts(disk)
2856rowsprocessed
数据块被重新读入BufferCache
如果有新的数据需要被读入BufferCache中,而BufferCache又没有足够的空闲空间,Oracle就根据LRU算法将LRU链表中LRU端的数据置换出去。当这些数据被再次访问到时,需要重新从磁盘读入。
SQL>selectowner,table_namefromt_test2
2whereowner='SYS';
718rowsselected.
ExecutionPlan
----------------------------------------------------------
Planhashvalue:1900296288
--------------------------------------------------------------------------------
|Id|Operation|Name|Rows|Bytes|Cost(%CPU)
|Time|
--------------------------------------------------------------------------------
|0|SELECTSTATEMENT||99|2178|10(0)
|00:00:01|
|1|TABLEACCESSBYINDEXROWID|T_TEST2|99|2178|10(0)
|00:00:01|
|*2|INDEXRANGESCAN|T_TEST2_IDX1|99||1(0)
|00:00:01|
--------------------------------------------------------------------------------
PredicateInformation(identifiedbyoperationid):
---------------------------------------------------
2-access("OWNER"='SYS')
Statistics
----------------------------------------------------------
0recursivecalls
0dbblockgets
145consistentgets
0physicalreads
0redosize
21690bytessentviaSQL*Nettoclient
902bytesreceivedviaSQL*Netfromclient
49SQL*Netroundtripsto/fromclient
0sorts(memory)
0sorts(disk)
718rowsprocessed
SQL>select*fromt_test1;--占用BufferCache
47582rowsselected.
ExecutionPlan
----------------------------------------------------------
Planhashvalue:1883417357
-----------------------------------------------------------------------------
|Id|Operation|Name|Rows|Bytes|Cost(%CPU)|Time|
-----------------------------------------------------------------------------
|0|SELECTSTATEMENT||47582|3996K|151(2)|00:00:02|
|1|TABLEACCESSFULL|T_TEST1|47582|3996K|151(2)|00:00:02|
-----------------------------------------------------------------------------
Statistics
----------------------------------------------------------
195recursivecalls
0dbblockgets
3835consistentgets
5physicalreads
0redosize
5102247bytessentviaSQL*Nettoclient
35277bytesreceivedviaSQL*Netfromclient
3174SQL*Netroundtripsto/fromclient
5sorts(memory)
0sorts(disk)
47582rowsprocessed
SQL>selectowner,table_namefromt_test2
2whereowner='SYS';
718rowsselected.
ExecutionPlan
----------------------------------------------------------
Planhashvalue:1900296288
--------------------------------------------------------------------------------
|Id|Operation|Name|Rows|Bytes|Cost(%CPU)
|Time|
--------------------------------------------------------------------------------
|0|SELECTSTATEMENT||99|2178|10(0)
|00:00:01|
|1|TABLEACCESSBYINDEXROWID|T_TEST2|99|2178|10(0)
|00:00:01|
|*2|INDEXRANGESCAN|T_TEST2_IDX1|99||1(0)
|00:00:01|
--------------------------------------------------------------------------------
PredicateInformation(identifiedbyoperationid):
---------------------------------------------------
2-access("OWNER"='SYS')
Statistics
----------------------------------------------------------
0recursivecalls
0dbblockgets
145consistentgets
54physicalreads
0redosize
21690bytessentviaSQL*Nettoclient
902bytesreceivedviaSQL*Netfromclient
49SQL*Netroundtripsto/fromclient
0sorts(memory)
0sorts(disk)
718rowsprocessed
全表扫描
当发生全表扫描(FullTableScan)时,用户进程读取表的数据块,并将他们放在LRU链表的LRU端(和上面不同,不是放在MRU端)。这样做的目的是为了使全表扫描的数据尽快被移出。因为全表扫描一般发生的频率较低,并且全表扫描的数据块大部分在以后都不会被经常使用到。
而如果你希望全表扫描的数据能被cache住,使之在扫描时放在MRU端,可以通过在创建或修改表(或簇)时,指定CACHE参数。
1.2.2逻辑读
逻辑读指的就是从(或者视图从)BufferCache中读取数据块。按照访问数据块的模式不同,可以分为即时读(CurrentRead)和一致性读(ConsistentRead)。注意:逻辑IO只有逻辑读,没有逻辑写。
即时读
即时读即读取数据块当前的最新数据。任何时候在BufferCache中都只有一份当前数据块。即时读通常发生在对数据进行修改、删除操作时。这时,进程会给数据加上行级锁,并且标识数据为“脏”数据。
SQL>select*fromt_test1whereowner='SYS'forupdate;
22858rowsselected.
ExecutionPlan
----------------------------------------------------------
Planhashvalue:3323170753
------------------------------------------------------------------------------
|Id|Operation|Name|Rows|Bytes|Cost(%CPU)|Time|
------------------------------------------------------------------------------
|0|SELECTSTATEMENT||22858|1919K|151(2)|00:00:02|
|1|FORUPDATE||||||
|*2|TABLEACCESSFULL|T_TEST1|22858|1919K|151(2)|00:00:02|
------------------------------------------------------------------------------
PredicateInformation(identifiedbyoperationid):
---------------------------------------------------
2-filter("OWNER"='SYS')
Statistics
----------------------------------------------------------
44recursivecalls
23386dbblockgets
2833consistentgets
0physicalreads
5044956redosize
2029221bytessentviaSQL*Nettoclient
17138bytesreceivedviaSQL*Netfromclient
1525SQL*Netroundtripsto/fromclient
0sorts(memory)
0sorts(disk)
22858rowsprocessed
一致性读
Oracle是一个多用户系统。当一个会话开始读取数据还未结束读取之前,可能会有其他会话修改它将要读取的数据。如果会话读取到修改后的数据,就会造成数据的不一致。一致性读就是为了保证数据的一致性。在BufferCache中的数据块上都会有最后一次修改数据块时的SCN。如果一个事务需要修改数据块中数据,会先在回滚段中保存一份修改前数据和SCN的数据块,然后再更新BufferCache中的数据块的数据及其SCN,并标识其为“脏”数据。当其他进程读取数据块时,会先比较数据块上的SCN和自己的SCN。如果数据块上的SCN小于等于进程本身的SCN,则直接读取数据块上的数据;如果数据块上的SCN大于进程本身的SCN,则会从回滚段中找出修改前的数据块读取数据。通常,普通查询都是一致性读。
下面这个例子帮助大家理解一下一致性读:
会话1中:
SQL>selectobject_namefromt_test1whereobject_id=66;
OBJECT_NAME
------------------------------
I_SUPEROBJ1
SQL>updatet_test1setobject_name='TEST'whereobject_id=66;
1rowupdated.
会话2中:
SQL>selectobject_namefromt_test1whereobject_id=66;
OBJECT_NAME
------------------------------
I_SUPEROBJ1
ExecutionPlan
----------------------------------------------------------
Planhashvalue:1883417357
-----------------------------------------------------------------------------
|Id|Operation|Name|Rows|Bytes|Cost(%CPU)|Time|
-----------------------------------------------------------------------------
|0|SELECTSTATEMENT||1|27|151(2)|00:00:02|
|*1|TABLEACCESSFULL|T_TEST1|1|27|151(2)|00:00:02|
-----------------------------------------------------------------------------
PredicateInformation(identifiedbyoperationid):
---------------------------------------------------
1-filter("OBJECT_ID"=66)
Statistics
----------------------------------------------------------
0recursivecalls
0dbblockgets
661consistentgets
0physicalreads
108redosize
423bytessentviaSQL*Nettoclient
385bytesreceivedviaSQL*Netfromclient
2SQL*Netroundtripsto/fromclient
0sorts(memory)
0sorts(disk)
1rowsprocessed
1.2.3查找数据
在一个查询操作中,大量的读操作都产生于数据的查找过程中。减少查找过程是我们优化IO性能问题的重要目标。
下面介绍几种主要的数据查找方式。
FullTableScan
当查询条件无法命中任何索引、或者扫描索引的代价大于全表扫描代价的某一比例时(由参数optimizer_index_cost_adj设定),Oracle会采用全表扫描的方式查找数据。当发生全表扫描时,Oracle会自下向上一次读取一定数量(由参数db_file_multiblock_read_count设定)的数据块,一直读取到高水位标志(HWM,HighWaterMark)下。FullTableScan会引起dbfilescatteredread事件。
INDEXUNIQUESCAN
全表扫描查找数据的效率是非常低的。而索引能大幅提高查找效率。普通索引的数据结构是B-Tree,树的叶子节点中包含数据的ROWID,指向数据记录,同时还有指针指向前一个/后一个叶子节点。索引扫描每次读取一个数据块,索引扫描是“连续的”(Sequential)。当索引为UNIQUE索引时,每个叶子节点只会指向一条数据。如果Oracle能预知扫描结果只有0或1条记录时,会采用INDEXUNIQUESCAN。当对UniqueIndex中的所有字段进行完全匹配时,会发生INDEXUNIQUESCAN。
SQL>selectobject_namefromt_test1
2whereobject_id=66;
ExecutionPlan
----------------------------------------------------------
Planhashvalue:2634232531
---------------------------------------------------------------------------------
|Id|Operation|Name|Rows|Bytes|Cost(%CPU)|
Time|
---------------------------------------------------------------------------------
|0|SELECTSTATEMENT||1|27|1(0)|
00:00:01|
|1|TABLEACCESSBYINDEXROWID|T_TEST1|1|27|1(0)|
00:00:01|
|*2|INDEXUNIQUESCAN|T_TEST1_PK|1||1(0)|
00:00:01|
---------------------------------------------------------------------------------
INDEXUNIQUESCAN的查找过程如下:
从数的根节点数据块开始查找;
查找根节点块中所有key值中大于或等于要查找的值的最小key值;
如果key值大于查找值,则继续查找这个key值之前一个key值所指向的子节点数据块;
如果key值等于查找值,则继续查找这个key值所指向的子节点数据块;
如果没有key值大于或等于查找值,则继续查找最大key值所指向的子节点数据块;
如果继续查找的节点数据块是数一个分支节点,则重复2~4步;
如果查找的节点是叶子节点数据块,则在数据块中查找等于查找值的key值;
如果找到相等的key值,则返回数据和ROWID;
如果没找到相等的key值,则说明没有符合条件的数据,返回NULL。
INDEXRANGESCAN
如果通过索引查找数据时,Oracle认为会返回数据可能会大于1,会进行INDEXRANGESCAN,例如UniqueIndex中字段不完全匹配查找时、非UniqueIndex查找时。
SQL>selectobject_namefromt_test1
2whereobject_id<66;
64rowsselected.
ExecutionPlan
----------------------------------------------------------
Planhashvalue:1635545337
---------------------------------------------------------------------------------
|Id|Operation|Name|Rows|Bytes|Cost(%CPU)|
Time|
---------------------------------------------------------------------------------
|0|SELECTSTATEMENT||57|1539|2(0)|
00:00:01|
|1|TABLEACCESSBYINDEXROWID|T_TEST1|57|1539|2(0)|
00:00:01|
|*2|INDEXRANGESCAN|T_TEST1_PK|57||1(0)|
00:00:01|
---------------------------------------------------------------------------------
INDEXRANGESCAN分为闭包(有前后查找边界)和非闭包(只有一边或者没有边界)。返回数据会依据索引增序排序,多个相同值则会按照ROWID的增序排序。以下的查找条件都是闭包的:
WHEREcolumn='Value'
WHEREcolumnlike'value%'
WHEREcolumnbetween'value1'and'value2'
WHEREcolumnin('value1','value2')
以下查找条件非闭包:
WHEREcolumn<'value1'
WHEREcolumn>'value2'
闭包条件下的INDEXRANGESCAN的查找过程如下:
从数的根节点数据块开始查找;
查找根节点块中所有key值中大于或等于要查找的起始值的最小key值;
如果key值大于起始值,则继续查找这个key值之前一个key值所指向的子节点数据块;
如果key值等于起始值,则继续查找这个key值所指向的子节点数据块;
如果没有key值大于或等于起始值,则继续查找最大key值所指向的子节点数据块;
如果继续查找的节点数据块是数一个分支节点,则重复2~4步;
如果查找的节点是叶子节点数据块,则在数据块中大于或等于要查找的起始值的最小key值;
如果Key值小于或等于结束值,则:如果所有Key字段都符合WHERE字句中的查找条件,则返回数据和ROWID;否则继续查找当前叶子节点所指向的右边的叶子节点。
INDEXUNIQUESCAN和INDEXRANGESCAN都会引起dbfilesequentialread事件。
TABLEACCESSBYINDEXROWID
当发生索引扫描时,如果需要返回的字段都在索引上,则直接返回索引上的数据,而如果还需要返回非索引上的字段的值,Oracle则需要根据从索引上查找的ROWID到对应的数据块上取回数据,这时就是TABLEACCESSBYINDEXROWID。
INDEXFASTFULLSCAN&INDEXFULLSCAN
索引快速全扫描和全表扫描类似,一次读取db_file_multiblock_read_count个数据块来描所有索引的叶子节点。INDEXFASTFULLSCAN和其他索引扫描不同,它不会从树的根节点开始读取,而是直接扫描所有叶子节点;也不会一次读取一个数据块,而是一次读取db_file_multiblock_read_count个数据块。INDEXFASTFULLSCAN会引起dbfilescatteredread事件。
SQL>selectcount(1)fromt_test1whereobject_id<21314;
ExecutionPlan
----------------------------------------------------------
Planhashvalue:1586700957
---------------------------------------------------------------------------------
|Id|Operation|Name|Rows|Bytes|Cost(%CPU)|Time|
---------------------------------------------------------------------------------
|0|SELECTSTATEMENT||1|4|24(5)|00:00:01|
|1|SORTAGGREGATE||1|4|||
|*2|INDEXFASTFULLSCAN|T_TEST1_PK|18264|73056|24(5)|00:00:01|
---------------------------------------------------------------------------------
在某些情况下,如db_file_multiblock_read_count值过小、强制使用索引扫描时,会发生INDEXFULLSCAN。INDEXFULLSCAN和INDEXFASTFULLSCAN不同,它是一种索引扫描,按照B-Tree的查找法从树的根节点开始扫描,遍历整棵树,并且一次读取一个数据块。它会引起dbfilesequentialread事件。
SQL>select/*+index(at_test1_pk)*/count(1)fromt_test1a;
ExecutionPlan
----------------------------------------------------------
Planhashvalue:138350774
-----------------------------------------------------------------------
|Id|Operation|Name|Rows|Cost(%CPU)|Time|
-----------------------------------------------------------------------
|0|SELECTSTATEMENT||1|61(2)|00:00:01|
|1|SORTAGGREGATE||1|||
|2|INDEXFULLSCAN|T_TEST1_PK|47582|61(2)|00:00:01|
-----------------------------------------------------------------------
相关文章推荐
- Oracle IO问题解析(七)
- Oracle IO问题解析(七)—— db file scattered read
- Oracle IO问题解析(一)
- Oracle IO问题解析(八)
- Oracle IO问题解析(五)—— Oracle中的IO问题及其解决思路
- Oracle IO问题解析(八)—— db file parallel read
- Oracle IO问题解析(九)—— 控制文件相关的IO事件
- Oracle IO问题解析(三)
- Oracle IO问题解析(六)—— 数据文件相关的IO事件
- Oracle IO问题解析
- Oracle IO问题解析(四)
- Oracle IO问题解析(三)—— IO系统的设计和配置(1)
- Oracle IO问题解析(九)
- Oracle IO问题解析
- Oracle IO问题解析(五)
- Oracle IO问题解析(十)
- Oracle IO问题解析(一)—— Oracle中IO的产生
- Oracle IO问题解析
- Oracle IO问题解析
- Oracle IO问题解析【io主文】