Loading...
  所在位置:论坛首页 -> 计算机科学与技术 -> Oracle专栏 -> 几条很基本的oracle操作(sql)常识
回复

几条很基本的oracle操作(sql)常识

作者:wanjianfei 时间:2009-4-28 17:08:27 收藏 编辑

昨天一个同事给的文档,很有用,简称降龙十八掌,因为不能上传附件就ctrl+c了。

第一掌 避免对列的操作

任何对列的操作都可能导致全表扫描,这里所谓的操作包括数据库函数、计算表达式等等,查询时要尽可能将操作移至等式的右边,甚至去掉函数。  

1:下列SQL条件语句中的列都建有恰当的索引,但30万行数据情况下执行速度却非常慢:  

select * from record where  substrb(CardNo,1,4)='5378'(13) 

select * from record where  amount/30< 100011秒) 

select * from record where  to_char(ActionTime,'yyyymmdd')='19991201'10秒) 

由于where子句中对列的任何操作结果都是在SQL运行时逐行计算得到的,因此它不得不进行表扫描,而没有使用该列上面的索引;如果这些结果在查询编译时就能得到,那么就可以被SQL优化器优化,使用索引,避免表扫描,因此将SQL重写如下:

select * from record where CardNo like  '5378%'< 1秒)

select * from record where amount  < 1000*30< 1秒)

select * from record where ActionTime= to_date ('19991201' ,'yyyymmdd')< 1秒)

差别是很明显的!

第二掌 避免不必要的类型转换

需要注意的是,尽量避免潜在的数据类型转换。如将字符型数据与数值型数据比较,ORACLE会自动将字符型用to_number()函数进行转换,从而导致全表扫描。

2:表tab1中的列col1是字符型(char),则以下语句存在类型转换:

select col1,col2 from tab1 where col1>10

应该写为: select col1,col2 from tab1 where col1>'10'

第三掌 增加查询的范围限制

增加查询的范围限制,避免全范围的搜索。

3:以下查询表record 中时间ActionTime小于200131日的数据:

       select * from record where ActionTime < to_date ('20010301' ,'yyyymm')

查询计划表明,上面的查询对表进行全表扫描,如果我们知道表中的最早的数据为200111日,那么,可以增加一个最小时间,使查询在一个完整的范围之内。修改如下: select * from record where

ActionTime < to_date ('20010301' ,'yyyymm')

and   ActionTime > to_date ('20010101' ,'yyyymm')

后一种SQL语句将利用上ActionTime字段上的索引,从而提高查询效率。把'20010301'换成一个变量,根据取值的机率,可以有一半以上的机会提高效率。同理,对于大于某个值的查询,如果知道当前可能的最大值,也可以在Where子句中加上 AND 列名< MAX(最大值)”。

第2楼 wanjianfei
编辑 删除 引用

第四掌 尽量去掉"IN""OR"

含有"IN""OR"Where子句常会使用工作表,使索引失效;如果不产生大量重复值,可以考虑把子句拆开;拆开的子句中应该包含索引。  

4     select count(*) from stuff where id_no in('0','1')23秒)

可以考虑将or子句分开:  

select count(*) from stuff where id_no='0' 

select count(*) from stuff where id_no='1'

然后再做一个简单的加法,与原来的SQL语句相比,查询速度更快。

回复时间:2009-4-28 17:12:52
第3楼 wanjianfei
编辑 删除 引用

第五掌 尽量去掉 "<>"

尽量去掉 "<>",避免全表扫描,如果数据是枚举值,且取值范围固定,则修改为"OR"方式。

5

UPDATE SERVICEINFO SET STATE=0 WHERE STATE<>0;

以上语句由于其中包含了"<>",执行计划中用了全表扫描(TABLE ACCESS FULL),没有用到state字段上的索引。实际应用中,由于业务逻辑的限制,字段state为枚举值,只能等于012,而且,值等于=12的很少,因此可以去掉"<>",利用索引来提高效率。

修改为:UPDATE SERVICEINFO SET STATE=0  WHERE STATE = 1 OR STATE = 2 进一步的修改可以参考第4种方法

回复时间:2009-4-28 17:13:44
第4楼 wanjianfei
编辑 删除 引用

第六掌 去掉Where子句中的IS NULLIS NOT NULL

Where字句中的IS NULLIS NOT NULL将不会使用索引而是进行全表搜索,因此需要通过改变查询方式,分情况讨论等方法,去掉Where子句中的IS NULLIS NOT NULL

回复时间:2009-4-28 17:14:31
第5楼 wanjianfei
编辑 删除 引用

第七掌 索引提高数据分布不均匀时查询效率

索引的选择性低,但数据的值分布差异很大时,仍然可以利用索引提高效率。A、数据分布不均匀的特殊情况下,选择性不高的索引也要创建。

ServiceInfo中数据量很大,假设有一百万行,其中有一个字段DisposalCourseFlag,取值范围为枚举值:[01234567]。按照前面说的索引建立的规则,“选择性不高的字段不应该建立索引,该字段只有8种取值,索引值的重复率很高,索引选择性明显很低,因此不建索引。然而,由于该字段上数据值的分布情况非常特殊,具体如下表:

取值范围

1~5

6

7

占总数据量的百分比

1%

98%

1%


回复时间:2009-4-28 17:16:24
第6楼 wanjianfei
编辑 删除 引用
而且,常用的查询中,查询DisposalCourseFlag<6 的情况既多又频繁,毫无疑问,如果能够建立索引,并且被应用,那么将大大提高这种情况的查询效率。因此,我们需要在该字段上建立索引。
回复时间:2009-4-28 17:17:14
第7楼 wanjianfei
编辑 删除 引用

第八掌 利用HINT强制指定索引

ORACLE优化器无法用上合理索引的情况下,利用HINT强制指定索引。

继续上面7的例子,ORACLE缺省认定,表中列的值是在所有数据行中均匀分布的,也就是说,在一百万数据量下,每种DisposalCourseFlag值各有12.5万数据行与之对应。假设SQL搜索条件DisposalCourseFlag=2,利用DisposalCourseFlag列上的索引进行数据搜索效率,往往不比全表扫描的高,ORACLE因此对索引“视而不见”,从而在查询路径的选择中,用其他字段上的索引甚至全表扫描。根据我们上面的分析,数据值的分布很特殊,严重的不均匀。为了利用索引提高效率,此时,一方面可以单独对该字段或该表用analyze语句进行分析,对该列搜集足够的统计数据,使ORACLE在查询选择性较高的值时能用上索引;另一方面,可以利用HINT提示,在SELECT关键字后面,加上“/*+ INDEX(表名称,索引名称)*/”的方式,强制ORACLE优化器用上该索引。

比如: select * from  serviceinfo where DisposalCourseFlag=1 ;

上面的语句,实际执行中ORACLE用了全表扫描,加上蓝色提示部分后,用到索引查询。如下:

select /*+  INDEX(SERVICEINFO,IX_S_DISPOSALCOURSEFLAG)  */  *

from  serviceinfo where DisposalCourseFlag=1;

请注意,这种方法会加大代码维护的难度,而且该字段上索引的名称被改变之后,必须要同步所有指定索引的HINT代码,否则HINT提示将被ORACLE忽略掉。

回复时间:2009-4-28 17:21:46
第8楼 wanjianfei
编辑 删除 引用

第九掌 屏蔽无用索引

继续上面8的例子,由于实际查询中,还有涉及到DisposalCourseFlag=6的查询,而此时如果用上该字段上的索引,将是非常不明智的,效率也极低。因此这种情况下,我们需要用特殊的方法屏蔽该索引,以便ORACLE选择其他字段上的索引。比如,如果字段为数值型的就在表达式的字段名后,添加“+ 0”,为字符型的就并上空串:“||""

如: select * from  serviceinfo where DisposalCourseFlag+ 0 = 6 and workNo =  '36'

不过,不要把该用的索引屏蔽掉了,否则同样会产生低效率的全表扫描。

回复时间:2009-4-28 17:22:35
第9楼 wanjianfei
编辑 删除 引用

第十掌 分解复杂查询,用常量代替变量

对于复杂的Where条件组合,Where中含有多个带索引的字段,考虑用IF语句分情况进行讨论;同时,去掉不必要的外来参数条件,减低复杂度,以便在不同情况下用不同字段上的索引。

继续上面9的例子,对于包含

Where (DisposalCourseFlag < v_DisPosalCourseFlag) or (v_DisPosalCourseFlag is null) and ....的查询,(这里v_DisPosalCourseFlag为一个输入变量,取值范围可能为[NULL01234567]),可以考虑分情况用IF语句进行讨论,类似:

IF v_DisPosalCourseFlag =1 THEN

Where DisposalCourseFlag = 1 and ....

ELSIF v_DisPosalCourseFlag =2 THEN

Where DisposalCourseFlag = 2 and .... 

。。。。。。

回复时间:2009-4-28 17:23:26
第10楼 wanjianfei
编辑 删除 引用

第十一掌 like子句尽量前端匹配

因为like参数使用的非常频繁,因此如果能够对like子句使用索引,将很高的提高查询的效率。

6select * from city where name like ‘%S%’

以上查询的执行计划用了全表扫描(TABLE ACCESS FULL),如果能够修改为:

select * from city where name like ‘S%’

那么查询的执行计划将会变成(INDEX RANGE SCAN),成功的利用了name字段的索引。这意味着Oracle SQL优化器会识别出用于索引的like子句,只要该查询的匹配端是具体值。因此我们在做like查询时,应该尽量使查询的匹配端是具体值,即使用like ‘S%’

回复时间:2009-4-28 17:24:12
第11楼 wanjianfei
编辑 删除 引用

第十二掌 Case语句合并多重扫描

我们常常必须基于多组数据表计算不同的聚集。例如下例通过三个独立查询:

81select count(*) from emp where sal<1000;

     2select count(*) from emp where sal between 1000 and 5000;

     3select count(*) from emp where sal>5000;

这样我们需要进行三次全表查询,但是如果我们使用case语句:

select

count (sale when sal <1000

then 1 else null end)             count_poor,

count (sale when between 1000 and 5000

then 1 else null end)             count_blue_collar,

count (sale when sal >5000

then 1 else null end)             count_poor

from emp;

这样查询的结果一样,但是执行计划只进行了一次全表查询。

回复时间:2009-4-28 17:35:07
高级回复

操作选项: 评分 加精 解精 奖惩 设专题 设公告 解公告 固顶 总固顶 解固顶 结帖 解结帖 锁帖 解锁 移帖 删帖
  首页
Copyright 2006-2012 HistoryCreator.com Powered By: BBSGood 5
吉ICP备06005902号