Sql调优clustering factor影响数据删除速度一例
Upcoming SlideShare
Loading in...5
×
 

Sql调优clustering factor影响数据删除速度一例

on

  • 672 views

 

Statistics

Views

Total Views
672
Views on SlideShare
672
Embed Views
0

Actions

Likes
0
Downloads
21
Comments
0

0 Embeds 0

No embeds

Accessibility

Categories

Upload Details

Uploaded via as Adobe PDF

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment

Sql调优clustering factor影响数据删除速度一例 Sql调优clustering factor影响数据删除速度一例 Document Transcript

  • SQL 调优:ClusteringFactor 影响数据删除速度 一例 by Maclean.liu liu.maclean@gmail.com www.oracledatabase12g.com
  • About Mel Email:liu.maclean@gmail.coml Blog:www.oracledatabase12g.coml Oracle Certified Database Administrator Master 10gand 11gl Over 6 years experience with Oracle DBA technologyl Over 7 years experience with Linux technologyl Member Independent Oracle Users Groupl Member All China Users Groupl Presents for advanced Oracle topics: RAC,DataGuard, Performance Tuning and Oracle Internal.
  • 事情是这样的,客户有一套核心的 10g 业务数据库,需要针对个别大表删除 2 年前的归档数据,这些表都是普通的堆表(heap table),没有使用分区或其他技术。因为考虑到不能影响在线业务,所以不能使用 insert append/rename 的方式来加速删除,只能老老实实地在匿名PL/SQL 块里通过 rowid 批量删除数据,虽然慢一些但还是能接受的,具体的 PL /SQL 块如下:DECLARE CURSOR table_name_cur IS SELECT /*+ FULL(a) */ a.rowid from table_name awhere time_column<required_date table_name_rec table_name_cur%ROWTYPE; row_number number;BEGIN row_number :=0; OPEN table_name_cur; LOOP FETCH table_name_cur INTO table_name_rec; IF table_name_cur%NOTFOUND THEN commit; EXIT; END IF; delete from table_name WHERE rowid = table_name_rec.rowid; row_number := row_number + 1; if (mod (row_number,1000) =0) then insert into delete_rows values (row_number); commit; end if; END LOOP; insert into delete_rows values (row_number); commit; CLOSE table_name_cur;END;/可以看到以上使用一个游标 FULL SCAN 目标数据表取出所需删除行的 rowid,之后在循环中不断 fetch 出 rowid 并实际删除数据。问题出在一张不是非常大的 LG 表上(不超过 10GB),删除这张 LG 表消耗的时间超过 10 个小时,而其他更大的表删除也仅用 2-3 个小时。针对这一反常现象,客户对删除操作做了 10046 level8 的跟踪,因为整个删除过程比较长, View slide
  • 所以仅 trace 了一小段时间,因为这个 trace 并不完整所以没有办法使用 tkprof 工具分析该trace。没办法,就分析裸 trace 信息吧。从 trace 内容来看,该时段内主要的等待是 db file sequence read(简称 DFSR)即数据文件单块读事件,一开始以为是表上有链式行/迁移行造成了大量的 DFSR,但客户日常有对该表执行chained rows analyze,没有发现该表上有明显的 chained/migrated rows 问题。具体观察该 DFSR 事件的 p1/p2 obj#参数发现这些数据文件单块读主要是针对该 LG 表的 2 个索引的,而且最为奇怪的是其中一个索引单块读的频率远多于另外一个索引,比例大约为60:1。这 2 个索引的差异表现,让我意识到得问题的所在,查看 dba_indexes 索引视图发现最近一次分析是在 4/18 日,而 2 个索引统计信息间最 大的差异不在于索引大小,而在于clustering_factor 也就是我们说的聚集因子, LG 表上大约有 6000 万条数据,索引 A 的clustering_factor 为 170 万,而索引 B 的 clustering_factor 达到了 3400 万,即 2 个索引的聚集因子差 20 倍,显然这时因为索引 A 上 column 更为有序(可能是 sequence)而索引 B 上的字段较为随机造成了这种反差。因为一开始使用 FULL SCAN 目标数据表来获取所需的 ROWID,所以在实际删除前相关数据行所在的表数据块已经被 FULL SCAN 读取到 buffer cache 中了,FULL SCAN 使用scattered read 多块读,在这里不是主要的性能瓶颈。最主要的问题在于,假设一个 table datablock 中有 20 行数据,对于 clustering_factor 较低的索引 A 而言可能这 20 行数据都映射到索引的一个 leaf block 中,而对于 clustering_factor 很高的索引 B 而言可能这 20 行数据需要映射到 20 个 leaf block 中,那么如果我们要删除这个数据块中的 20 行数据,就需要访问索引 A上的 1 个 leaf 块和索引 B 上的 20 个 leaf 块,因为这些都是历史归档数 据,所以日常已经没有业务访问这些 old leaf block 了,所以这部分的叶子块几乎不会在 buffer cache 中,服务进程需要把它们”一一”(这里真的是一一,一次读一块)从 disk 上读取到缓存中;最糟糕的还不止于此,因为实例的 buffer cache 有限,索引 B 上的 leaf block 在读入后可能很快被踢出buffer cache,而因为 table 与索引 B 间数据的无序性,这些 leaf block 可能需要在后续的删除中再次访问,这将对索引 B 的物理读取代价大大放大了。这种代价反映在 Oracle waitinterface 上就是用户频繁地看到针对某个索引的”db file sequential read”等待事件。我们通过还原现场,来深入了解一下 clustering factor 对于删除的影响: View slide
  • 首先要构建一张有一列极端有序,而一列极端无序的数据表SQL> select * from v$version;BANNER----------------------------------------------------------------Oracle Database 10g Enterprise Edition Release 10.2.0.4.0 - 64biPL/SQL Release 10.2.0.4.0 - ProductionCORE 10.2.0.4.0 ProductionTNS for Linux: Version 10.2.0.4.0 - ProductionNLSRTL Version 10.2.0.4.0 - ProductionSQL> create table sample nologging tablespace users as select rownum t1 fromdual connect by level<=900000;Table created.SQL> alter table sample add t2 number;Table altered.SQL> update sample set t2=dbms_random.value(1,999999999999999);900000 rows updated.SQL> commit;Commit complete.SQL> create index ind_t1 on sample(t1) nologging;Index created.SQL> create index ind_t2 on sample(t2) nologging;Index created.SQL> exec dbms_stats.gather_table_stats(MACLEAN,SAMPLE,cascade=>TRUE);PL/SQL procedure successfully completed.SQL> select blocks,NUM_ROWS from dba_tables where table_name=SAMPLE; BLOCKS NUM_ROWS---------- ---------- 13213 900000SQL> select CLUSTERING_FACTOR,LEAF_BLOCKS,DISTINCT_KEYS,index_name fromdba_indexes where table_name=SAMPLE;CLUSTERING_FACTOR LEAF_BLOCKS DISTINCT_KEYS INDEX_NAME----------------- ----------- ------------- ------------------------------ 899782 4148 896896 IND_T2 14975 2004 900000 IND_T1/* 以上构建了一张 90 万行的数据表,共 13213 个 block T1 列通过序列产生,较为有序 T2 列通过随机数产生,无序 ind_t1 索引构建在 t1 列上,clustering_factor 较低 14975,接近表上数据块的总数 ind_t2 索引构建在 t2 列上,clustering_factor 为 899782,接近表上数据行的总数*/SQL> alter session set events 10046 trace name context forever,level 8;Session altered.
  • SQL> set timing on;DECLARE CURSOR table_name_cur IS SELECT /*+ FULL(a) */ a.rowid from sample a wheret1<=900000; table_name_rec table_name_cur%ROWTYPE; row_number number;BEGIN row_number :=0; OPEN table_name_cur; LOOP FETCH table_name_cur INTO table_name_rec; IF table_name_cur%NOTFOUND THEN commit; EXIT; END IF; delete from sample WHERE rowid = table_name_rec.rowid; row_number := row_number + 1; if (mod (row_number,1000) =0) then insert into delete_rows values (row_number); commit; end if; END LOOP; insert into delete_rows values (row_number); commit; CLOSE table_name_cur;END;/Elapsed: 00:03:28.52观察其 trace 文件,可以发现在多次 EXEC/FETCH 后就会紧跟一个 db file sequential read 等待事件众所周知 db file sequential read 等待事件在如v$session/V$session/v$active_session_history等动态性能视图中的 p1 代表 file 号,p2 为 block 号,p3 为读取 block 总数,一般为 1而在 10046 trace 中可以直接看到 file#,block#,blocks 和 obj#,为了分辨单块读的对象,可以直接从obj#了解SQL> select object_id,object_name,object_type from dba_objects where object_namein (SAMPLE,IND_T1,IND_T2); OBJECT_ID OBJECT_NAME OBJECT_TYPE---------- -------------------- ------------------- 1307548 IND_T1 INDEX 1307549 IND_T2 INDEX 1307547 SAMPLE TABLEWAIT #3: nam=db file sequential read ela= 283 file#=6 block#=3311 blocks=1obj#=1307549 tim=1275797217728516EXEC #3:c=999,e=349,p=1,cr=2,cu=8,mis=0,r=1,dep=1,og=1,tim=1275797217728552FETCH #2:c=0,e=5,p=0,cr=1,cu=0,mis=0,r=1,dep=1,og=1,tim=1275797217728578EXEC #3:c=0,e=49,p=0,cr=1,cu=8,mis=0,r=1,dep=1,og=1,tim=1275797217728641
  • FETCH #2:c=0,e=4,p=0,cr=1,cu=0,mis=0,r=1,dep=1,og=1,tim=1275797217728663EXEC #3:c=0,e=36,p=0,cr=1,cu=8,mis=0,r=1,dep=1,og=1,tim=1275797217728712FETCH #2:c=0,e=3,p=0,cr=1,cu=0,mis=0,r=1,dep=1,og=1,tim=1275797217728732WAIT #3: nam=db file sequential read ela= 205 file#=6 block#=3956 blocks=1obj#=1307549 tim=1275797217728979EXEC #3:c=0,e=265,p=1,cr=1,cu=8,mis=0,r=1,dep=1,og=1,tim=1275797217729010FETCH #2:c=0,e=5,p=0,cr=1,cu=0,mis=0,r=1,dep=1,og=1,tim=1275797217729036[oracle@rh2 udump]$ cat g10r2_ora_5190.trc|grep "db file sequential read"|wc -l72395[oracle@rh2 udump]$ cat g10r2_ora_5190.trc|grep "db file sequential read"|grep1307549|wc -l67721[oracle@rh2 udump]$ cat g10r2_ora_5190.trc|grep "db file sequential read"|grep1307548|wc -l3878/* 以上 object_id 1307549 对应为较高 clustering_factor 的索引 IND_T2, 该索引发生了绝大多数 db file sequential read 等待 而 object_id 1307548 对应为较低 clustering_factor 的索引 IND_T1, 该索引发生了较少量的 db file sequential read 等待*/SQL> selectsql_id,executions,disk_reads,user_io_wait_time/1000000,elapsed_time/1000000 2 from v$sql 3 where sql_text=DELETE FROM SAMPLE WHERE ROWID = :B1 ;SQL_ID EXECUTIONS DISK_READS USER_IO_WAIT_TIME/1000000ELAPSED_TIME/1000000------------- ---------- ---------- ---------------------------------------------31m4m2drt2t5m 900000 74936 67.862581147.743482[oracle@rh2 udump]$ tkprof g10r2_ora_5190.trc 5190.tkf sys=noDELETE FROM SAMPLEWHERE ROWID = :B1call count cpu elapsed disk query current rows------- ------ -------- ---------- ---------- ---------- ---------- ----------Parse 1 0.00 0.00 0 0 0 0Execute 900000 78.67 147.73 74936 916440 6401613 900000Fetch 0 0.00 0.00 0 0 0 0------- ------ -------- ---------- ---------- ---------- ---------- ----------total 900001 78.67 147.73 74936 916440 6401613 900000Misses in library cache during parse: 1Misses in library cache during execute: 1Optimizer mode: ALL_ROWSParsing user id: 64 (recursive depth: 1)Elapsed times include waiting on following events: Event waited on Times Max. Wait Total Waited ---------------------------------------- Waited ---------- ------------ SQL*Net message to client 1 0.00 0.00 SQL*Net message from client 1 21.99 21.99
  • db file sequential read 72362 0.05 67.60 db file scattered read 543 0.00 0.25 log file switch completion 29 0.97 5.81 free buffer waits 268 0.01 2.83 latch: cache buffers lru chain 3 0.00 0.00 latch: object queue header operation 5 0.00 0.00 log file switch (checkpoint incomplete) 22 0.97 8.46 latch: In memory undo latch 2 0.00 0.00 latch: cache buffers chains 1 0.00 0.00可能是受限于固化的思维,在我的潜意识中总是觉得 clustering_factor 聚集因子只会影响select 查询语句,而忽略了其对 update/delete 操作的影响;事实是 clustering_factor(注意它只是一个统计信息指标,而非参数)反映了数据在表中的随机分布 程度,当表上的数据分布无序时表和索引间的交叉访问将显得很糟糕,这种交叉访问并不局限于查询语句(一个典型可能是 INDEX RANGE SCAN-TABLE ACCESS BY INDEX ROWID),也可能发生在 DML 操作所隐含的维护索引操作中。显然除了通过以某些列的顺序整理表外没有太好的方法来降低 clustering_factor,但实际上这样做是不可能的。首先定期有序化整理的成 本过高了,其次如果表上有多个单列上的不同索引,如我们上述演示中的 t1、t2 列,如果以 t2 列的顺序整理表那么一个很可能的结果是 t1列上的索引的 clustering factor 猛增,如:SQL> create table ordered_sample nologging tablespace users as select * Fromsample order by t2;Table created.SQL> truncate table sample;Table truncated.SQL> insert /*+ append */ into sample select * from ordered_sample;900000 rows created.SQL> commit;Commit complete.SQL> exec dbms_stats.gather_table_stats(MACLEAN,SAMPLE,cascade=>TRUE);PL/SQL procedure successfully completed.SQL> select clustering_factor,index_name from dba_indexes wheretable_name=SAMPLE;CLUSTERING_FACTOR INDEX_NAME----------------- ------------------------------ 899782 IND_T1 3983 IND_T2
  • /* 可以看到 ind_t2 所以的 clustering_factor 的确下降了,换得的是 ind_t1 对应的增长 */针对由该 clustering_factor 引起的低效率批量 delete/update 操作,我们可以通过以下措施减少”db file sequential read”等待的出现: 1. 通过 keep cache 保留池等技术将 clustering_factor 过高的索引缓存住,以避免频繁地 单块物理读,从而提高性能 2. 如果你正在执行一个大作业,那么可以暂时将 clustering_factor 过高的索引 drop 掉, 在完成操作后再重建该索引,这样起到加速作业的目的rebuild 重建索引在以上案例的情景中获益并不大。另外”db file sequential read”单块读等待是一种最为常见的物理 IO 等待事件,这里的sequential 指的是将数据块读入到相连的内存空间中(contiguous memory space),而不是指所读取的数据块是连续的。该 wait event 可能在以下情景中发生: 1. 最为常见的是执行计划中包含了 INDEX FULL SCAN/UNIQUE SCAN,此时出现”db file sequential read”等待是预料之中的,一般不需要我们去特别关注 2. 当执行计划包含了 INDEX RANGE SCAN-(“TABLE ACCESS BY INDEX ROWID”/”DELETE”/”UPDATE”), 服务进程将按照”访问索引->找到 rowid->访问 rowid 指定的表数据块并执行必要的操作”顺序访问 index 和 table,每次物理 读取都会 进入”db file sequential read”等待,且每次读取的都是一个数据块;这种情况下 clustering_factor 将发挥其作用,需要我们特别去关注,本例中提及的解决方法对 这 种情景也有效 3. Extent boundary,假设一个 Extent 区间中有 33 个数据块,而一次”db file scattered read”多块读所读取的块数为 8,那么在读取这个区间时经过 4 次多块读取后,还剩下 一个数据块,但是请记住多块读 scattered read 是不能跨越一个区间的(span an extent),此时就会单块读取并出现”db file scattered read”。这是一种正常现象,一般不 需要额外关注
  • 4. 假设某个区间内有 8 个数据块,它们可以是块 a,b,c,d,e,f,g,h,恰好当前系统中除了 d 块外的其他数据块都已经被缓存在 buffer cache 中了,而这时候恰好要访问这个区间 中的数据,那么此时就会单块读取 d 这个数据块,并出现”db file sequential read”等 待。注意这种情况不仅于表,也可能发生在索引上。这是一种正常现象,一般不需要 额外关注5. chained/migrated rows 即链式或迁移行,这里我们不介绍链式行的形成原 因,chained/migrated rows 会造成服务进程在 fetch 一行记录时需要额外地单块读取, 从而出现”db file sequential read”。这种现象需要我们特别去关注,因为大量的链式/迁 移行将导致如 FULL SCAN 等操作极度恶化(以往的经验是一张本来全表扫描只需要 30 分钟的表,在出现大量链式行后,全表扫描需要数个小时),同时也会对其他操作 造成不那么 明显的性能影响。可以通过监控 v$sysstat 视图中的”table fetch continued row”操作统计来了解系统中链式/迁移行访问的情况,还可以通过 DBA_TBALES 视图 中的 CHAIN_CNT 来了解表上的链式/迁移行情况,当然这 要求定期收集表上的统计 信息;如果没有定期收集的习惯,那么可以配合@?/rdbms/admin/utlchain 脚本和 analyze table list chained rows 命令来获取必要的链式行信息6. 创建 Index entry,显然当对表上执行 INSERT 操作插入数据时,虽然在执行计划中你 看不到过多的细节,但实际上我们需要利用索引来快速验证表上的某些约束是否 合 理,还需要在索引的叶子块中插入相关的记录,此时也可能出现”db file sequential read”等待事件,当然这还和具体的插入的方式有关系。这是一种正常现象,一般不需 要额外关注7. 针对表上的 UPDATE/DELETE,不同于之前提到的”INDEX RANGE SCAN- UPDATE/DELETE”,如果我们使用 rowid 去更新或删除数据时,服务进程会先访问 rowid 指向的表块(注意是先访问 table block)上的行数据,之后会根据该行上的具体数 据去访问索引叶子块(注意 Oracle 并不知道这些 leaf block 在哪里,所以这里同样要如 range-scan/unique-scan 那样去访问 index branch block),这些访问都将会是单块读取, 并会出现’db file sequential read’,完成必要的读取后才会执行更新或删除的实际 EXEC 操作,如下例:
  • 以下 trace 中,obj#=1307547 为 sample 表,而 obj#=1307549 为 sample 表上的唯一一个索引PARSING IN CURSOR #10 len=58 dep=0 uid=64 oct=6 lid=64 tim=1275805024007795hv=505118268 ad=d387e470update sample set t2=t2+1 where rowid=AAE/OzAAEAAANUEAAQEND OF STMTPARSE #10:c=1999,e=3016,p=1,cr=1,cu=0,mis=1,r=0,dep=0,og=1,tim=1275805024007787WAIT #10: nam=db file sequential read ela= 314 file#=4 block#=54532 blocks=1obj#=1307547 tim=1275805024008308WAIT #10: nam=db file sequential read ela= 206 file#=6 block#=20 blocks=1obj#=1307549 tim=1275805024009235WAIT #10: nam=db file sequential read ela= 206 file#=6 block#=742 blocks=1obj#=1307549 tim=1275805024009496WAIT #10: nam=db file sequential read ela= 207 file#=6 block#=24 blocks=1obj#=1307549 tim=1275805024009750EXEC #10:c=2000,e=2297,p=6,cr=2,cu=8,mis=0,r=1,dep=0,og=1,tim=1275805024010210--实际的 UPDATE 发生在这里当大量执行这类 UPDATE/DELETE 操作时将需要频繁地交叉访问表和索引,如果恰好表上的某个索引有较高的 clustering_factor 的话,那么就会形成本例中的这种性能问题了。实际上当表上有较多索引时,使用 rowid 来批量 update/delete 数据这种方式是不被推荐的,仅当表上没有索引时才可能十分高效。如果你坚持要这样做,那么可以参照上面提到的建议。8.BUG!BUG!已知在 9i RAC 及 10g 中使用 ASM 的情况下,存在引发在适用情况下不使用”scattered read”多块读而去使用”sequential read”的 BUG。如果你的问题和上述情景都不匹配,但又有大量的”db file sequential read”等待事件,那么你有可能遇到 bug 了。在这里列出部分已知 bug: VersionBug# AffectedBug 7243560 – High “db file sequential read” IO times when using 10.2.0.4/11.1.ASM 0.7Bug 7243560: RAPID INCREASE IN DB FILE SEQUENTIAL READ 10.2.0.3AFTER MOVING TO ASMBug 9711810: EXCESSIVE DB FILE SEQUENTIAL READS WITH NON 9.2.0.8COMPLIANT BUFFER CACHE ON RACBug 9276739: INSERT STATEMENT HAS SLOW PERFORMANCE WITH 10.2.0.4DB FILE SEQUENTIAL READBug 8625100: EXCESSIVE DB FILE SEQUENTIAL READ ON UNDO 10.2.0.4Bug 8669544: HIGH DB FILE SEQUENTIAL READ AND GC CR DISK 10.2.0.4READ WAIT EVENTS DURING FULL SCANBug 7427133: AN INSERT CAUSES LOTS OF ‘DB FILE SEQUENTIAL 9.2.0.8READ’ WAITS FOR THE INDEX BLOCKSBug 8493139: INCREASE IN DB FILE SEQUENTIAL READ WAITEVENT 10.2.0.4AFTER MIGRATING TO 10 RAC/ASM
  • Bug 5882268: PERFORMANCE ISSUE WITH ‘DB FILE SEQUENTIAL 10.2.0.2READ’Bug 7415702: LOTS OF ‘DB FILE SEQUENTIAL READ’ ON UNDO 10.2.0.3Bug 5607724: 10202 DB FILE SEQUENTIAL READ THRICE AFTER 10.2.0.2UPGRADE FROM 9I© 2011, www.oracledatabase12g.com. 版权所有.文章允许转载,但必须以链接方式注明源地址,否则追求法律责任.