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

  • 395 views
Uploaded on

 

More in: Technology
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Be the first to comment
    Be the first to like this
No Downloads

Views

Total Views
395
On Slideshare
0
From Embeds
0
Number of Embeds
0

Actions

Shares
Downloads
21
Comments
0
Likes
0

Embeds 0

No embeds

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
    No notes for slide

Transcript

  • 1. SQL 调优:ClusteringFactor 影响数据删除速度 一例 by Maclean.liu liu.maclean@gmail.com www.oracledatabase12g.com
  • 2. 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.
  • 3. 事情是这样的,客户有一套核心的 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 的跟踪,因为整个删除过程比较长,
  • 4. 所以仅 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 对于删除的影响:
  • 5. 首先要构建一张有一列极端有序,而一列极端无序的数据表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.
  • 6. 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
  • 7. 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
  • 8. 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
  • 9. /* 可以看到 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”。这是一种正常现象,一般不 需要额外关注
  • 10. 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 操作,如下例:
  • 11. 以下 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
  • 12. 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. 版权所有.文章允许转载,但必须以链接方式注明源地址,否则追求法律责任.