根据statspack报表优化oracle数据库实例之“DB file scattered read”
扫描二维码
随时随地手机看文章
根据statspack报表优化oracle数据库实例之“DB file scattered read”
oracle的等待事件是衡量oracle运行状况的重要依据及指标。
等待事件的概念是在oracle7.0.1.2中引入的,大致有100个等待事件。在oracle8.0中这个数目增加到了大约150个,在oracle8i中大约有200个事件,到oracle9i时,等待事件增加到360个。
Oracle的等待事件主要有两种类型,即空闲(idle)等待事件和非空闲(non-idle)等待事件。空闲事件指oracle正在等待某种工作,在诊断和优化数据库的时候,我们不用过多注意这部分事件。
非空闲等待事件专门针对oracle的活动,指数据库任务或应用运行过程中发生的等待,这些等待事件是我们在调整数据库的时候应该关注与研究的。
常见的非空闲等待事件有:db file scattered read; db file sequential read; buffer busy waits; free buffer waits; enqueue; latch free; log file parallel write; log file sync.
Db file scattered read的产生
本文主要解释了db file scattered read-DB文件分散读取等待事件产生的原因与优化的方法。
Db file scattered read等待事件通常显示与全表扫描相关的等待。
当数据库进行全表扫描时,基于性能的考虑,数据会分散(scattered)读入buffer cache。如果这个等待事件笔记哦显著,可能说明对于某些全表扫描的表,没有创建索引或者索引没有有效利用。我们可能需要检查这些数据表以便确定是否进行了正确的设置。
然而这个等待事件并不总意味着性能底下,在某些条件下oracle会主动使用全表扫描来替换索引扫描以提高性能,这和访问的数据量有关,在CBO下oracle会进行更为智能的选择,在RBO下oracle更倾向于使用索引。
因为全表扫描被置于LRU(least recently used)列表的冷端(cold end),对于频繁访问的较小的数据表,可以选择把他们cache到内存中,以避免反复读取。
有两种方法可以帮助我们找出全表扫描较多的sql语句。
Statspack的报表
Top 5 Timed Events
~~~~~~~~~~~~~~~~~~ % Total
Event Waits Time (s) Ela Time
-------------------------------------------- ------------ ----------- --------
CPU time 246 50.79
db file sequential read 98,012 208 43.01
db file scattered read 1,001 11 2.20
direct path write 2,171 7 1.52
control file parallel write 1,404 3 .56
―――――――――
Buffer Gets Executions Gets per Exec %Total Time (s) Time (s) Hash Value
--------------- ------------ -------------- ------ -------- --------- ----------
4,392,146 1 4,392,146.0 39.6 64.73 247.08 719265629
Module: msmdsrv.exe
SELECT "DW"."D_TIME_DAY"."DAY_ID" , "DW"."D_PRODUCT_FUNCTION_SIM
S"."PRODUCT_FUNCTION_ID" , "DW"."D_PRODUCT_SCREEN_SIMS"."PRODUCT
_SCREEN_ID" , "DW"."D_PRODUCT_BRAND_SIMS"."ALL_ID" , "DW"."D_PRO
DUCT_BRAND_SIMS"."PRODUCT_BRAND_ID" , "DW"."D_ORG_STORE_CHANNEL_
TYPE_SIMS"."ALL_ID" , "DW"."D_ORG_STORE_CHANNEL_TYPE_SIMS"."CHAN
1,092,126 6 182,021.0 9.8 8.54 8.33 2845961438
Module: JDBC Thin Client
select
bs.model_name, decode(grouping(bs.fu
nction_name)+grouping(bs.spec_name)+ grouping(bs.screen_name)+gr
ouping(bs.model_name), 1,bs.function_name
||bs.spec_name ||bs.screen_name ||'小计',
625,322 2 312,661.0 5.6 16.21 24.55 4097549484
Module: JDBC Thin Client
select
bs.spec_name, decode(grouping(bs.fun
ction_name)+grouping(bs.spec_name)+ grouping(bs.screen_name),
1 ,bs.function_name ||bs.spec_name ||'小
计', 2, bs.function_name ||'小计',
604,296 14 43,164.0 5.4 15.89 15.55 3794571418
Module: JDBC Thin Client
select decode(grouping(ttt.category_name)+groupin
g(ttt.function_name)+grouping(ttt.spec_name)+grouping(ttt.model_
name)+grouping(ttt.is_master),0,ttt.model_name,2,ttt.category_na
me||'-'||ttt.function_name||'-'||ttt.spec_name||'-小计',3,ttt.ca
tegory_name||'-'||ttt.function_name||'-小计',4,ttt.category_name
560,367 29 19,323.0 5.1 2.72 2.74 1125417254
select r.ID,r.NAME,r.INFORMATION from USER_USERGROUP u,ROLE r,US
ERGROUP_ROLE
d u.USERID= :1
520,226 90 5,780.3 4.7 11.52 14.24 645606369
Module: JDBC Thin Client
BEGIN ctl.pkg_public_int.get_data(:1,:2,:3,:4) ; END;
367,777 2 183,888.5 3.3 3.10 3.75 1644183172
Module: JDBC Thin Client
select
bs.model_name, decode(grouping(bs.fu
nction_name)+grouping(bs.spec_name)+ grouping(bs.screen_name)+gr
ouping(bs.model_name), 1,bs.function_name
||bs.spec_name ||bs.screen_name ||'小计',
360,107 2 180,053.5 3.2 2.80 2.74 2652674913
―――――――――――――――
注意到以上很多查询导致的Buffer Gets都非常庞大,我们非常有理由怀疑索引存在问题,甚至缺少必要的索引。以上记录的是sql的片段,通过hash value值结合v$sqltext我们可以获得完整的sql语句。
SELECT * FROM v$sqltext WHERE hash_value = 4097549484
ORDER BY piece
V$session_longops动态性能视图
V$session_longops动态性能视图中记录了长时间运行(超过6秒)的事务,可能很多是全表扫描操作(不管怎样,这部分信息都值得我们注意)。当db file scattered read等待时间比较显著时,可以结合v$session_longops视图来进行诊断。
1, 检索出长时间运行事件相关表
SELECT target,COUNT(*) c FROM v$session_longops
GROUP BY target
ORDER BY c DESC
Target c
ODS.SM_SALES 26
DW.FS_DISTRIBUTION_BRANCH_PRODUCT 16
ODS.TL_PICKLISTITEM 13
ODS.CR_SHIPMENT_ITEM 12
ODS.CR_SHIPMENT_STATUS 12
ODS.CR_ARP_PLAN 11
ODS.CR_FACILITY_DAILY_PSI_SUMMARY 11
ODS.CR_RECEIPT_BALANCE_DAILY_D 11
ODS.CR_SHIPMENT_ATTRIBUTE 11
ODS.CR_ORDER_INFO 11
ODS.CR_ORDER_HEADER 11
TODS.CR_RECEIPT_ATTRIBUTE 11
TODS.CR_PARTY_ATTRIBUTE 11
ODS.CR_SHIPMENT 9
ODS.CR_CUSTOMER_DAILY_PSI_SUMMARY 6
ODS.FI_REPORT_DATA_H 6
FBI.LOG_AP 3
CTL.ETL_LOG 2
ODS.FI_V_HUIKUAN 2
(stale) obj# 303378 1
ODS.CR_INVENTORY_ITEM_VARIANCE 1
DW.F_EXPIATION_CRM 1
2, 检索得到长时间执行事务的具体sql语句的hash_value
SELECT DISTINCT sql_hash_value FROM v$session_longops
WHERE target = 'ODS.SM_SALES'
375479500
3850935052
3, 找出具体执行sql语句
SELECT * FROM v$sqltext WHERE hash_value = 375479500
ORDER BY piece
整理得到的sql语句结果,
SELECT TO_CHAR(S.ENTRY_DATE, 'yyyymmdd'),
IO.STORE_ID,
IPI.PRODUCT_ID,
SUM(S.QUANTITY),
SUM(S.TOTAL_AMOUNT),
SUBSTR(IPI.PRODUCT_ID, 1, 10),
SUM(SUM(S.QUANTITY)) OVER(PARTITION BY TO_CHAR(S.ENTRY_DATE, 'yyyymm'), IO.STORE_ID, IPI.PRODUCT_ID ORDER BY TO_CHAR(S.ENTRY_DATE, 'yyyymmdd')),
SUM(SUM(S.TOTAL_AMOUNT)) OVER(PARTITION BY TO_CHAR(S.ENTRY_DATE, 'yyyymm'), IO.STORE_ID, IPI.PRODUCT_ID ORDER BY TO_CHAR(S.ENTRY_DATE, 'yyyymmdd'))
FROM ODS.I_PRODUCT_INFO IPI,
ODS.I_ORG_STORE_RELATIONSHIP IO,
ODS.SM_SALES S
WHERE S.PRODUCT_ID = IPI.PRODUCT_ID
AND S.MARKET_PLACE_ID = IO.STORE_ID
AND TO_CHAR(S.ENTRY_DATE, 'yyyymmdd') BETWEEN '20081108' AND '20081110'
GROUP BY S.ENTRY_DATE, IPI.PRODUCT_ID, IO.STORE_ID
4,并在pl/sql develop开发工具中获得执行语句的查询执行计划如下。
SELECT STATEMENT, GOAL = CHOOSE
WINDOW SORT
SORT GROUP BY
NESTED LOOPS OUTER
NESTED LOOPS
TABLE ACCESS FULL ODS SM_SALES
INDEX UNIQUE SCAN ODS I_ORG_STORE_RELATIONSHIP_PK
INDEX UNIQUE SCAN ODS I_PRODUCT_INFO_PK
5, 分析查询相关的几个源数据表
表名称
Row number
关联字段索引
ODS.I_PRODUCT_INFO IPI
23779
Yes
ODS.I_ORG_STORE_RELATIONSHIP IO
9632
Yes
ODS.SM_SALES S
6147142
Yes
从对源表的分析数据我们可以看到,目前执行方式的问题有两个,首先是使用大表(拥有600万条以上记录的ods.sm_sales)做了嵌套循环的驱动表;其次,就是这个大表上的索引并没有得到合理的利用。从而导致本语句的执行时间25秒。
6,优化方法
首先我们可以使用oracle的hint,强制在大表ods.sm_sales上使用索引。其次由于在这三个表关联时,另外两个小表ipi和io都是需要跟中间大表s进行关联,所以使用nested loop将无法有效使用更多的索引进行关联,所以建议使用ordered,use_hash结合swap_join_input使得查询按照hash join方式,并在两次hashjoin时都将小表放在驱动表的位置上。执行优化后的语句入下。优化后执行时间为5秒 。
SELECT /*+ index(s SM_SALES_I5) ordered use_hash(ipi,s,io) swap_join_inputs(io)*/
TO_CHAR(S.ENTRY_DATE, 'yyyymmdd'),
IO.STORE_ID,
IPI.PRODUCT_ID,
SUM(S.QUANTITY),
SUM(S.TOTAL_AMOUNT),
SUBSTR(IPI.PRODUCT_ID, 1, 10),
SUM(SUM(S.QUANTITY)) OVER(PARTITION BY TO_CHAR(S.ENTRY_DATE, 'yyyymm'),IO.STORE_ID, IPI.PRODUCT_ID ORDER BY TO_CHAR(S.ENTRY_DATE, 'yyyymmdd')),
SUM(SUM(S.TOTAL_AMOUNT)) OVER(PARTITION BY TO_CHAR(S.ENTRY_DATE, 'yyyymm'),IO.STORE_ID, IPI.PRODUCT_ID ORDER BY TO_CHAR(S.ENTRY_DATE, 'yyyymmdd'))
FROM ODS.I_PRODUCT_INFO IPI ,
ODS.SM_SALES S,
ODS.I_ORG_STORE_RELATIONSHIP IO
WHERE S.MARKET_PLACE_ID = IO.STORE_ID
AND S.PRODUCT_ID = IPI.PRODUCT_ID
AND TO_CHAR(S.ENTRY_DATE, 'yyyymmdd') BETWEEN '20081108' AND '20081110'
GROUP BY S.ENTRY_DATE, IPI.PRODUCT_ID, IO.STORE_ID