您的位置:首页 > 数据库 > Oracle

Oracle IO问题解析(七)

2012-05-17 14:14 295 查看
3.2.2dbfilescatteredread
这是另外一个常见的引起数据库IO性能问题的等待事件。它通常发生在Oracle将“多数据块”读取到BufferCache中的非连续(分散的Scattered)区域。多数据块读就是我们上述所说的一次读取“DB_FILE_MULTIBLOCK_READ_COUNT”块数据块,前面提到,它通常发生在全表扫描(FullTableScan)和快速全索引扫描(FastFullIndexScan)时。当发现dbfilescatteredread等待事件是系统引起IO性能的主要原因时,我们可以采取以下措施对系统进行优化。
3.2.2.1优化存在FullTableScan和FastFullIndexScan的SQL语句

我们可以首先从statspack或者awr报告中的“SQLorderedbyReads”部分中找出存在FullTableScan和FastFullIndexScan的TopSQL。因为这些TopSQL往往是整个系统的瓶颈。
从9i开始,我们还可以通过视图V$SQL_PLAN来查找系统中存在FullTableScan和FastFullIndexScan的SQL语句。查找FullTableScan的语句:

selectsql_textfromv$sqlareat,v$sql_planp

wheret.hash_value=p.hash_valueandp.operation='TABLEACCESS'

andp.options='FULL';


查找FastFullIndexScan的语句

selectsql_textfromv$sqlareat,v$sql_planp

wheret.hash_value=p.hash_valueandp.operation='INDEX'

andp.options='FULLSCAN';


FullTableScan通常是由于以下几个原因引起的:

条件字段上没有索引;

在这种情况下,如果表的数据量比较大,我们就需要在相应字段上建立起索引。

CBO中,对象的统计数据不正确

CBO中,如果对象的统计数据或者其柱状图(Histogram)信息不正确,会导致优化器计算出错误的查询计划,从而选择全表扫描。这种情况下,我们要做的就重新分析(Analyze)表、索引及字段。

CBO中,SQL语句中引用到了无法估算统计数据的对象

在PLSQL中,可以建立一些高级的数据类型,如“TABLEOF”、ARRAY等,通过TABLE、CAST函数可以在SQL语句中将这些对象当成表来处理。而这些对象的数据只存在于调用PLSQL的会话中,因此他们没有相应的统计数据,Oracle会为他们生产一些假的统计数据以完成查询计划代价估算。但是基于这些假的数据计算出的查询计划一般是错误的。我们可以考虑通过提示来强制SQL使用索引或者强制SQL采用RBO优化器。
此外,如果SQL中引用到了临时表(TemporaryTable)也会产生同样的问题。其原因和解决方法和上面相同。

优化器认为索引扫描代价过高;

在Oracle中存在一个参数optimizer_index_cost_adj,该参数的值代表一个百分数,如果对索引扫描的代价达到或超过全表扫描的代价的这个百分比值时,优化器就采用全表扫描。
optimizer_index_cost_adj是一个全局性的参数,它的合理值是通过长期调整出来的。一般来说是一个介于1到100之间的数字。我们可以按照以下方法来选取optimizer_index_cost_adj的合理值。
先由以下语句得出optimizer_index_cost_adj的一个初始值:

SQL>select

2a.average_wait"AverageWaitsFTS"

3,b.average_wait"AverageWaitsIndexRead"

4,a.total_waits/(a.total_waits+b.total_waits)"PercentofFTS"

5,b.total_waits/(a.total_waits+b.total_waits)"PercentofIndexScans"

6,(b.average_wait/a.average_wait)*100"optimizer_index_cost_adj"

7from

8v$system_eventa,

9v$system_eventb

10wherea.EVENT='dbfilesequentialread'

11andb.EVENT='dbfilescatteredread';


AverageWaitsFTSAverageWaitsIndexReadPercentofFTSPercentofIndexScans

-----------------------------------------------------------------------------

optimizer_index_cost_adj

------------------------

1.251.06.041867874.958132126

84.8


这里,84.8是我们系统的初始值。在系统经过一段时间运行后,再次运行上面的语句,重新调整optimizer_index_cost_adj的值。经过多次如此反复的调整之后,最终上面语句得出值趋于稳定,这时这个值就是符合我们系统性能需求的最合理的值。
当然这个数值也可以通过statspack的历史数据来调整,在9i中:

selectto_char(c.end_interval_time,'MM/DD/YYYY')"Date",

sum(a.time_waited_micro)/sum(a.total_waits)/10000"AverageWaitsFTS",

sum(b.time_waited_micro)/sum(b.total_waits)/10000"AverageWaitsIndexRead",

(sum(a.total_waits)/sum(a.total_waits+b.total_waits))*100"PercentofFTS",

(sum(b.total_waits)/sum(a.total_waits+b.total_waits))*100"PercentofIndexScans",

(sum(b.time_waited_micro)/sum(b.total_waits))/

(sum(a.time_waited_micro)/sum(a.total_waits))*100"optimizer_index_cost_adj"

fromdba_hist_system_eventa,dba_hist_system_eventb,dba_hist_snapshotc

wherea.event_name='dbfilescatteredread'

andb.event_name='dbfilesequentialread'

anda.snap_id=c.snap_id

andb.snap_id=c.snap_id

groupbyc.end_interval_time

orderby1;


10g中:

selectto_char(c.snap_time,'MM/DD/YYYY')"Date",

sum(a.time_waited_micro)/sum(a.total_waits)/10000"AverageWaitsFTS",

sum(b.time_waited_micro)/sum(b.total_waits)/10000"AverageWaitsIndexRead",

(sum(a.total_waits)/sum(a.total_waits+b.total_waits))*100"PercentofFTS",

(sum(b.total_waits)/sum(a.total_waits+b.total_waits))*100"PercentofIndexScans",

(sum(b.time_waited_micro)/sum(b.total_waits))/

(sum(a.time_waited_micro)/sum(a.total_waits))*100"optimizer_index_cost_adj"

fromstats$system_eventa,stats$system_eventb,stats$snapshotc

wherea.event='dbfilescatteredread'

andb.event='dbfilesequentialread'

anda.snap_id=c.snap_id

andb.snap_id=c.snap_id

groupbyc.snap_time

orderby1;


当optimizer_index_cost_adj的值对于整个系统来说已经是比较合理的值,而某些语句由于该值选择了全表扫描扫描导致了IO性能问题时,我们可以考虑通过提示来强制语句命中索引。

建立在条件字段上的索引的选择性不高,结合上一条导致全表扫描;

当索引的选择性不高,且其代价过高,系统则会选择全表扫描来读取数据。这时我们可以考虑通过选择/建立选择性比较高的索引,使查询命中索引从而避免全表扫描。

SQL>createindext_test1_idx1ont_test1(owner)computestatistics;


Indexcreated.


SQL>setautottrace

SQL>selectobject_name

2fromt_test1

3whereowner='SYS'

4andcreated>sysdate-30;


norowsselected



ExecutionPlan

----------------------------------------------------------

Planhashvalue:1883417357


-----------------------------------------------------------------------------

|Id|Operation|Name|Rows|Bytes|Cost(%CPU)|Time|

-----------------------------------------------------------------------------

|0|SELECTSTATEMENT||49|1715|152(2)|00:00:02|

|*1|TABLEACCESSFULL|T_TEST1|49|1715|152(2)|00:00:02|

-----------------------------------------------------------------------------


PredicateInformation(identifiedbyoperationid):

---------------------------------------------------


1-filter("OWNER"='SYS'AND"CREATED">SYSDATE@!-30)


......


SQL>createindext_test1_idx2ont_test1(owner,created)computestatistics;


Indexcreated.


SQL>selectobject_name

2fromt_test1

3whereowner='SYS'

4andcreated>sysdate-30;


norowsselected



ExecutionPlan

----------------------------------------------------------

Planhashvalue:3417015015


--------------------------------------------------------------------------------

|Id|Operation|Name|Rows|Bytes|Cost(%CPU)

|Time|

--------------------------------------------------------------------------------

|0|SELECTSTATEMENT||49|1715|2(0)

|00:00:01|

|1|TABLEACCESSBYINDEXROWID|T_TEST1|49|1715|2(0)

|00:00:01|

|*2|INDEXRANGESCAN|T_TEST1_IDX2|49||1(0)

|00:00:01|

-------------------------------------------------------------------------------


PredicateInformation(identifiedbyoperationid):

---------------------------------------------------


2-access("OWNER"='SYS'AND"CREATED">SYSDATE@!-30)


......


3.2.2.2调整DB_FILE_MULTIBLOCK_READ_COUNT
当SQL已经没有优化余地后,问题仍没有解决,我们可以考虑调整DB_FILE_MULTIBLOCK_READ_COUNT大小。其作用我们在3.1.2中有做叙述,这里不再赘述。不过要注意一点就是,DB_FILE_MULTIBLOCK_READ_COUNT*DB_BLOCK_SIZE是一次IO读取的传输量,它不能大于系统的max_io_size大小。
从Oracle10gR2开始,如果没有设置DB_FILE_MULTIBLOCK_READ_COUNT的大小,Oracle会自动为其调整一个默认值,这个默认值的大小与平台最大IO大小(max_io_size)相关(对大多数平台来说max_io_size是1M),其大小被设置为(max_io_size/DB_BLOCK_SIZE)。
3.2.2.3将频繁访问的全扫描的表CACHE住
由于通过FullTableScan和FastFullIndexScan读取的数据块会被放置到BufferCache的LRU链表的LRU端,从而使数据块尽快从BufferCache中移出。因此,对于那些会被频繁访问到全扫描的表,且其数据量不大的情况下,我们可以考虑将它们CACHE住。

SQL>altertablet_test1cache;


Tablealtered.

对于FastFullIndexScan的索引对象,则可以考虑把它放置在KEEP池中。

SQL>alterindext_test1_idx1storage(buffer_poolkeep);


Indexaltered.


利用V$SESSION_EVENT视图,我们同样可以找到当前系统中发生全扫描的对象。

SQL>selectp1"fileid",p2"block_id",p3"block_num"

2fromv$session_wait

3whereevent='dbfilescatteredread';


fileidblock_idblock_num

------------------------------

35915297216


SQL>select

2segment_name"SegmentName",

3segment_type"SegmentType",

4block_id"FirstBlockofSegment",

5block_id+blocks"LastBlockofSegment"

6fromdba_extents

7where&fileid=file_id

8and&block_id>=block_id

9and&block_id<=block_id+blocks;

Entervalueforfileid:359

old7:where&fileid=file_id

new7:where359=file_id

Entervalueforblock_id:152972

old8:and&block_id>=block_id

new8:and152972>=block_id

Entervalueforblock_id:152972

old9:and&block_id<=block_id+blocks

new9:and152972<=block_id+blocks


SegmentName

--------------------------------------------------------------------------------

SegmentTypeFirstBlockofSegmentLastBlockofSegment

-------------------------------------------------------------

CSS_TP_SHMT_QUEUE

TABLE152969153001

3.2.2.4利用分区表减少全扫描操作读取的数据块数量
前面我们有介绍分区裁剪(PartitionPruning)技术。将表分区,利用分区裁剪技术,在进行全扫描时只会扫描在WHERE条件中出现的分区,从而可以减少全扫描所读取到的数据块数量。
3.2.2.5Housekeep历史数据
同样,housekeep不需要的、历史的数据,减少数据段中的数据块数量,也能减少全扫描的IO请求次数
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  Oracle 数据 系统