设为首页 收藏本站
查看: 581|回复: 0

[经验分享] Oracle索引技术的应用与剖析

[复制链接]

尚未签到

发表于 2016-7-24 11:08:04 | 显示全部楼层 |阅读模式
Oracle索引技术的应用与剖析
最近这段时间,总是想写一些有关性能调优的文章。但是苦于没有一个实际的案例,本人又不愿空谈理论,因为这些理论随便在网上就能找到,而且基本上千篇一律,因为理论上的那些东西就那么多,再怎么讲也不如一个实际案例生动。还好上天不负有心人,前些天让我碰到了一个实际的案例。这个实际案例是这样,本人所在的城市的某个政府职能部门,要推出一项新的服务,这项服务就是要向社会发放一张功能完备的CPU卡片,通过这张卡片可以经办该政府部门所负责相关业务,在某种程度上为广大市民提供了很大方便。在制作这张卡片过程中,需要将用户的身份证,姓名以及个人照片打印在卡片上,并且要在卡片的芯片内写入一些信息。因此出现了两个问题,第一、该政府职能部门的自身的业务系统数据库中,没有公民个人照片信息以及卡片内部所需的一些信息;第二、在该政府职能部门的数据库内,存在着一些错误信息,比如:身份证号码错误、姓名错误等等。为了解决这个问题,该政府部门用户提出了一个方案,那就是拿本系统中的个人基本信息与当地公安系统的人口信息通过身份证号码进行比对,并且将能够比对上的人员所需的并且本系统没有的个人关键信息,从人口数据库中取得,同时将没有比对上的个人信息进行标记,以便下发由当事人到相关部门去修正。由于该政府部门的所有业务系统,都是由本人所在的公司确切的说是由本人设计并组织开发的,因此理所当然本人有幸承接这个“神圣而伟大”的任务。
刚拿到这个需求时,觉得这个需求还是比较简单的,就是一个对照比对,然后将能够匹配的记录所需要的数据拿到。因此我将本系统中的数据到一张表中,该表设计脚本如下:createtableTEDA_RYXX(AAC002 VARCHAR2(18),AAC001 VARCHAR2(14) notnull,AAC003 VARCHAR2(20),AAC004 VARCHAR2(3),AAC005 VARCHAR2(3),AAC006 DATE,AAC009 VARCHAR2(3),AAB003 VARCHAR2(15),AAB004 VARCHAR2(50),FLAG VARCHAR2(3),NAMEVARCHAR2(20),SEX VARCHAR2(3),SEXMC VARCHAR2(6),CSRQ VARCHAR2(10),HYZK VARCHAR2(3),HYZKMC VARCHAR2(10),MZ VARCHAR2(3),MZMC VARCHAR2(6),PHOTOID VARCHAR2(20),)tablespaceTJLMDATApctfree10initrans1maxtrans255storage(initial64Kminextents1maxextentsunlimited);altertableTEDA_RYXXaddconstraintPK_TEDA_AAC001 primarykey(AAC001)usingindextablespaceTJLMDATApctfree10initrans2maxtrans255storage(initial64Kminextents1maxextentsunlimited);
在这张表中蓝色字段是从本系统数据中导出的,红色字段事要到人口库中比对并取得的,flag字段是个标志字段,对比对上的人员进行标记。因此我带着这这张表兴高采烈的到了公安系统的人口数据库中,去完成这个看似简单到极点的任务。为了进行比对我写了类似如下的存储过程:
……
cursor info is
select t1.xm,t1.xb,t1.xbmc,t1.csrq,t1.mz,t1.mzmc,t1.hyzk,
t1.hyzkmc,t1.photoid,t2.aac001
from T1 t1,T2 t2
where t1.sfzhm=t2.aac002;
/*
其中T1是人口库中人员基本信息表,T2是我带到哪里去比对的人员信息表,双方通过身份证号码进行关联
*/
begin
for c in info loop
update T2
set name=c.xm,sex=c.xb,sexmc=c.xbmc,csrq=c.csrq,mz=c.mz,mzmc=c.mzmc,hyzk=c.hyzk,hyzkmc=c.hyzkmc,
phototid=c.photoid,flag=’1’
where aac001=c.aac001;
end loop;
commit;
exception
when others then
rollback;
end;
这个过程在执行时运行了大约半个小时,然后安全的返回了,我本以为任务完成了,但是当我检查T1表时,却发现没任何信息被比对上,这怎么可能?难道真的就是没有匹配的记录吗?这不可能!我非常坚定,于是我查看了Oracle的在线日志,发现Update操作在执行了一段时间后戛然而止,我又查看了警告日文件,发现了那个著名的Ora1555错误。这是由于内存不足引起的,因此我判断这是由于关联后出现了大量的匹配记录,这些匹配记录在进行update操作时出现了回滚段充满而又没被释放的情况,因此出现了Ora1555。这怎么办?没什么作为公司研发中心的技术总监,我当然有办法。我马上重写了一个过程,这个过程类似代码如下:
……
v_csnum number(5);
cursor info is
select t2.aac001t2.aac002
from T2 t2
where t1.sfzhm=t2.aac002;
begin
for c in info loop
select count(1) from T1 where sfzhm=c.aac002;
if (v_csnum=1) then
……
else
update T2 set flag=’2’ where aac001=c.aac001
end if;
end loop;
commit;
exception
when others then
rollback;
end;
这个过程的思想就是查询出T2表中的主键和身份证号码字段,然后循环这个结果集,通过身份证去逐条比对,这样看上去好像效率不高,但是在T1表中的身份证字段sfzhm在人口数据库中是一个索引字段,而且游标只查询一张表的两个字段,并且在更新时是通过主键更新的,效率也很高,并且不会浪费太多空间。也就是说这个过程充分利用了数据库的索引字段来完成操作。在运行这个过程半个多小时后,他安全的返回了,当我满心欢喜的去检查结果时,又一件让我捉摸不透的事情发生了,这个过程只比对成功了9700条记录,还剩下几十万人根本没有参与运算。这又是怎么回事?于是我将已经比对上的人员导出,又执行了一下,结果还是一样只比对上了9千人左右,因此我怀疑这个过程每次只运行前一万条记录,后面的根本就不参与运算。这样也行啊,不就二十几万人吗,执行几次就可以了!可是我的如意算盘又落空了,但执行到第五次时,过程以闪电般的速度返回了,而且没有任何错误信息。但是却一条记录也没有比对上。在运行以下小果还是一样,我赶忙查询了日志信息,发现没有任何错误,只是打开了很多游标。对于for循环方式打开游标,是由Oracle自动完成的,而且它的关闭也是由Oracle控制的。因此我怀疑每次循环Oracle都会打开一次游标,而且关闭的不太及时,另外在这个数据库中对游标的打开最大数量还有限制。这时时间已经很晚了,当天的工作只好先结束,我带着郁闷的心情不得不离开。
第二天早上我到公司继续思考,昨天的问题,突然有一个想法涌上心头。可不可以利用分页查询的办法来解决这个问题呢?在应用系统开发中,分页查询是经常采用的一项技术,就是当记录数很多时,不是一次性全部查出,而是分批分次查询,每次查询控制一定的查询数量。我想这个方法也能解决我遇到的问题,因此我针对这个想法,又采用了一些调优手段,写出了类似如下的过程:
procedure fetchmatchperson(prm_appcode outnumber,
prm_errmsgoutvarchar2)iscursor grepinfo(prm_start number, prm_end number) isselect /*+ordered use_nl(t,teda_ryxx)*/aac001, aac002from(selectridfrom(selectrownum rn, ridfrom(selectrowidridfromteda_ryxxwheresource= 'le' andobject_type = 'ma'orderbyaac001 desc)whererownum <= prm_end)wherern >= prm_start) t,teda_ryxxwheret.rid = teda_ryxx.rowid;v_xm ac01.aac003%type;v_xb ac01.aac004%type;v_csrq ac01.aac006%type;v_mz ac01.aac005%type;csnum number(2);endnum number(10);innerstart number(5);innerend number(5);v_aac001 teda_ryxx.aac001%type;v_aac002 teda_ryxx.aac002%type;beginprm_appcode := def_ok;endnum := 0;innerstart:= 0;innerend := 0;while(endnum <= 20000) loopinnerstart := innerend;innerend := innerend + 1000;opengrepinfo(innerstart, innerend);loopfetchgrepinfointov_aac001, v_aac002;exitwhengrepinfo %notfound;selectcount(1) intocsnum fromac01 whereaac002 = v_aac002;if(csnum = 1) thenselectaac003, aac004, aac006, aac005intov_xm, v_xb, v_csrq, v_mzfromac01whereaac002 = v_aac002;updateteda_ryxxsetname= v_xm,sex= v_xb,csrq = v_csrq,mz = v_mz,flag = '1'whereaac001 = v_aac001;elseupdateteda_ryxx setflag = '2' whereaac001 = v_aac001;endif;endloop;closegrepinfo;commit;endnum := endnum + innerend;endloop;commit;exceptionwhenothersthenprm_appcode := def_error;prm_errmsg:= sqlerrm;end;

首先我在原信息表中也就是上面所说的T2表,这里的teda_ryxx表中增加了两个字段,sourceobject_type并通过update语句赋予相应的值分别是’le’’ma’,然后在aac001,source,object_type上建立了复合索引INDX——MA,如下:
create indexINDX_MA onTEDA_RYXX (SOURCE,OBJECT_TYPE,AAC001)tablespaceINDXpctfree10initrans2maxtrans255storage(initial64Kminextents1maxextentsunlimited);然后我写出了上面过程蓝色部分的那个带参数的游标,而且这次采用open/close方式来控制游标的打开与关闭。这个游标为什么这样来写,在讲解这个之前我需要讲两个专题。第一就是在Oracle中实现分页的技术,在Oracle中实现分页查询,是通过rownum关键字来实现的,rownum代表一个表中的记录数,比如我要查询一个表中的前100条记录,可以这样写:
select * from (select row_.*,rownum rownum_
from (select * from tablename) row_
where rownum<=100)
where rownum_>0;
通过这句查询我们就可以实现分页查询,并且我们可以将分页两个端点作为参数传入语句中,这样就可实现逐批地查询了。
第二我要讲讲Oracle索引技术,这项技术在我们这个方案中是一个决定成败的关键技术,索引的使用对于各种Oracle调优也是非常关键的。Oracle采用树形结构的索引而且索引是有序的,我们在日常的查询中,对索引的使用应该有以下5种情况:
1、 索引唯一扫描:通过主键或者唯一索引来查询记录,这种方式也是效率最高的。
2、 索引范围扫描:这种方式发生在返回多个值时,如通过where id> and id <,或者非唯一索引id=''等语句,范围扫描要求返回的结果集不能太大,否则将不会使索引发挥作用。
3、 索引全扫描:Oracle会按照索引的顺序全部扫描该索引,类似全表扫描,效率不高。
4、 索引快速全扫描:它不按照索引顺序来访问,而是直接读取索引块来访问索引数据。在这种存取方法中可以利用多块读功能,该种情况一般出现在select count(*)这种情况中。
5、 索引跳跃式扫描:这是9i之后的新功能,一般出现在复合索引中,如在字段(A1,A2)上建立符合索引,当执行where A2=value查询时,Oracle会跳过A1,直接使A2使用这个符合索引。(这个功能在实践中尚待考量)
Oracle中的索引,其实是rowid的一个逻辑名,在通过索引进行检索时,都是先检索到索引代表的rowid值,然后通过rowid回表检索目标数据。如果我们要检索的数据就在索引字段中,那么我们通过索引就能获得数据,也就不用回表检索了,那么效率将会更加提高,因此我们要在上面的source,object_type,aac001字段上建立符合索引,而且将aac001作为排序字段,我们的思路是利用索引,但是在索引上先排序,再分页,选择完rowid,再回表查询(我们这里其实不用回表再查询了)。因此我们的游标构造如下:
cursor grepinfo(prm_start number, prm_end number) isselect /*+ordered use_nl(t,teda_ryxx)*/aac001, aac002from(selectridfrom(selectrownum rn, ridfrom(selectrowidridfromteda_ryxxwheresource= 'le' andobject_type = 'ma'orderbyaac001 desc)whererownum <= prm_end)wherern >= prm_start) t,teda_ryxxwheret.rid = teda_ryxx.rowid;在对索引排序时要注意符合索引的所有列必须都要用到,而且顺序要与索引定义顺序一样,否则order by 将会弃用索引。另外由于我们在索引的rowid上先进行了排序,因此不会出现分页查询中的逻辑读积增的问题。所谓逻辑读积增,就是第一次分页读100条记录加入逻辑读是100,那么第10次读逻辑读就是1000,但是却只返回了900到1000的100条记录,因此对性能造成损害,合理的使用索引以及合理利用索引特性,利用了索引是有序的,所以先对索引排序分页,在回表查询,这样就避免了逻辑读积增。这个游标的每一次的执行计划类似下面这样:
select statement
view
count stopkey
view
table access by index rowd
index range scan descending
可以看出这里成功的使用了stopkey内部关键字,限制了每次的返回记录数,另外每次对表的检索都是通过索引完成的,大大提高了效率。另外在这个游标中我还使用了hint线索/*+ordered use_nl(t,teda_ryxx)*/通过这个线索,可以实现将每次的执行计划稳定下来,避免不必要的硬解析。这在高可用的OLTP环境中是必要的。
  通过以上这些技术的采用,成功地完成了这个比对工作,而且比对程序在数据库中高效并且平滑的运行,没有对系统造成性能损害。特此将这次的心得与体会贡献出来,供大家参考。

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-248568-1-1.html 上篇帖子: Oracle表:数据定义语言(DDL) 下篇帖子: Oracle优化器的优化方式(转载)
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表