1 如何掌握编写一套高质量SQL之本领--优化主题系列

来源:互联网 发布:windows视频播放器 编辑:程序博客网 时间:2024/05/06 03:22

     SQL的编写优化技巧网上资料浩如烟海,也有很好的一些总结性资料,目前总结了以下部分,可供参考。但真正搞懂SQL最优,掌握编写一套高质量SQL之本领,定当要对SQL的执行原理进行深刻的挖掘与剖析。

至此,本人将分章节逐步介绍:基数与选择性、直方图、聚簇因子、统计信息、执行计划、访问路径(全表扫描、rowid扫描、索引唯一扫描、索引范围扫描、索引跳跃扫描、索引全扫描、索引快速全扫描等等)、JOIN方法(嵌套循环、哈希连接、排序合并连接)、特殊连接(笛卡尔积、外连接、半连接、反连接)、成本计算(全表扫描成本计算、索引扫描成本计算、嵌套循环成本计算)、查询变换、视图合并、谓词推入、优化技巧及案例分析等。分享这一主题系列,以便读者能短时间掌握优化知识,能编写一套高质量的SQL打下坚实基础。

 

以下为总结的部分条目,请参考。

 

1.对查询进行优化,要尽量避免全表扫描,首先应考虑在whereorder by涉及的列上建立索引。


2.
应尽量避免在 where子句中对字段进行 null值判断,否则将导致引擎放弃使用索引而进行全表扫描,如:

select id from t where num is null

最好不要给数据库留NULL,尽可能的使用 NOTNULL填充数据库.

备注、描述、评论之类的可以设置为 NULL,其他的,最好不要使用NULL

不要以为 NULL 不需要空间,比如:char(100) 型,在字段建立时,空间就固定了, 不管是否插入值(NULL也包含在内),都是占用 100个字符的空间的,如果是varchar这样的变长字段, null 不占用空间。


可以在num上设置默认值0,确保表中num列没有null值,然后这样查询:

select id from t where num = 0


3.
应尽量避免在 where子句中使用 != <>操作符,否则将引擎放弃使用索引而进行全表扫描。

4.
应尽量避免在 where子句中使用 or来连接条件,如果一个字段有索引,一个字段没有索引,将导致引擎放弃使用索引而进行全表扫描,如:

select id from t where num=10 or Name = 'admin'

可以这样查询:

select id from t where num = 10

union all

select id from t where Name = 'admin'

 

5.统一SQL语句的写法

对于以下两句SQL语句,程序员认为是相同的,数据库查询优化器认为是不同的。 

select * from dual; 
select * From dual;

 

其实就是大小写不同,查询分析器就认为是两句不同的SQL语句,必须进行两次解析。生成2个执行计划。所以作为程序员,应该保证相同的查询语句在任何地方都一致,多一个空格都不行!

 

6.不要把SQL语句写得太复杂

我经常看到,从数据库中捕捉到的一条SQL语句打印出来有2A4纸这么长。一般来说这么复杂的语句通常都是有问题的。我拿着这2页长的SQL语句去请教原作者,结果他说时间太长,他一时也看不懂了。可想而知,连原作者都有可能看糊涂的SQL语句,数据库也一样会看糊涂。

一般,将一个Select语句的结果作为子集,然后从该子集中再进行查询,这种一层嵌套语句还是比较常见的,但是根据经验,超过3层嵌套,查询优化器就很容易给出错误的执行计划。因为它被绕晕了。像这种类似人工智能的东西,终究比人的分辨力要差些,如果人都看晕了,我可以保证数据库也会晕的。

另外,执行计划是可以被重用的,越简单的SQL语句被重用的可能性越高。而复杂的SQL语句只要有一个字符发生变化就必须重新解析,然后再把这一大堆垃圾塞在内存里。可想而知,数据库的效率会何等低下。

 

7. 减少访问数据库的次数:
Oracle
在内部执行了许多工作:解析SQL语句,估算索引的利用率,绑定变量 , 读数据块等。


8.in
not in也要慎用,否则会导致全表扫描,如:

select id from t where num in(1,2,3)

对于连续的数值,能用between就不要用in了:

select id from t where num between 1 and3

很多时候用 exists代替 in 是一个好的选择:

select num from a where num in(select num from b)

用下面的语句替换:

select num from a where exists(select 1 from b wherenum=a.num)

 

9.下面的查询也将导致全表扫描:

select id from t where name like ‘%abc%’

若要提高效率,可以考虑全文检索。

10.
如果在 where子句中使用参数,也会导致全表扫描。因为SQL只有在运行时才会解析局部变量,但优化程序不能将访问计划的选择推迟到运行时;它必须在编译时进行选择。然而,如果在编译时建立访问计划,变量的值还是未知的,因而无法作为索引选择的输入项。如下面语句将进行全表扫描:

select id from t where num = @num

可以改为强制查询使用索引:

select id from t with(index(索引名)) where num = @num

 

11.应尽量避免在where子句中对字段进行表达式操作,这将导致引擎放弃使用索引而进行全表扫描。如:

select id from t where num/2 = 100

应改为:

select id from t where num = 100*2


12.
应尽量避免在where子句中对字段进行函数操作,这将导致引擎放弃使用索引而进行全表扫描。如:

select id from t where substring(name,1,3) =’abc’       -–nameabc开头的id

select id from t where datediff(day,createdate,’2005-11-30′) =0    -–‘2005-11-30’    --生成的id

应改为:

select id from t where name like 'abc%'

select id from t where createdate >= '2005-11-30' andcreatedate < '2005-12-1'


13.
不要在 where子句中的“=”左边进行函数、算术运算或其他表达式运算,否则系统将可能无法正确使用索引。

14.
在使用索引字段作为条件时,如果该索引是复合索引,那么必须使用到该索引中的第一个字段作为条件时才能保证系统使用该索引,否则该索引将不会被使用,并且应尽可能的让字段顺序与索引顺序相一致。

15.
不要写一些没有意义的查询,如需要生成一个空表结构:

select col1,col2 into #t from t where 1=0

这类代码不会返回任何结果集,但是会消耗系统资源的,应改成这样:
create table #t(…)

 

16.Update 语句,如果只更改12个字段,不要Update全部字段,否则频繁调用会引起明显的性能消耗,同时带来大量日志。

17.要清楚执行计划

执行计划是数据库根据SQL语句和相关表的统计信息作出的一个查询方案,这个方案是由查询优化器自动分析产生的,比如一条SQL语句如果用来从一个 10万条记录的表中查1条记录,那查询优化器会选择索引查找方式,如果该表进行了归档,当前只剩下5000条记录了,那查询优化器就会改变方案,采用全表扫描方式。

可见,执行计划并不是固定的,它是个性化的。产生一个正确的执行计划有两点很重要:

(1)    SQL语句是否清晰地告诉查询优化器它想干什么?

(2)    查询优化器得到的数据库统计信息是否是最新的、正确的?

 

18.使用临时表暂存中间结果

简化SQL语句的重要方法就是采用临时表暂存中间结果,但是,临时表的好处远远不止这些,将临时结果暂存在临时表,后面的查询就在tempdb中了,这可以避免程序中多次扫描主表,也大大减少了程序执行中共享锁阻塞更新锁,减少了阻塞,提高了并发性能。

 

19.OLTP系统SQL语句必须采用绑定变量 

select *from orderheader where changetime >'2010-10-2000:00:01' 
select * from orderheader where changetime >'2010-09-2200:00:01'

 

以上两句语句,查询优化器认为是不同的SQL语句,需要解析两次。如果采用绑定变量

select *from orderheader where changetime >@chgtime

@chgtime变量可以传入任何值,这样大量的类似查询可以重用该执行计划了,这可以大大降低数据库解析SQL语句的负担。一次解析,多次重用,是提高数据库效率的原则。

 

20.绑定变量窥测

事物都存在两面性,绑定变量对大多数OLTP处理是适用的,但是也有例外。比如在where条件中的字段是倾斜字段的时候。

倾斜字段指该列中的绝大多数的值都是相同的,比如一张人口调查表,其中民族这列,90%以上都是汉族。那么如果一个SQL语句要查询30岁的汉族人口有多少,那民族这列必然要被放在where条件中。这个时候如果采用绑定变量@nation会存在很大问题。

试想如果@nation传入的第一个值是汉族,那整个执行计划必然会选择表扫描。然后,第二个值传入的是布依族,按理说布依族占的比例可能只有万分之一,应该采用索引查找。但是,由于重用了第一次解析的汉族的那个执行计划,那么第二次也将采用表扫描方式。这个问题就是著名的绑定变量窥测,建议对于倾斜字段不要采用绑定变量。

 

21.数据类型的隐式转换对查询效率的影响

如下列A表和B表做表连接查询,a.cardvarchar2类型,b.idnumber类型,在关联连接时,则会产生隐士转换。

select a.card,a.type,b.name from a,b where a.card = b.id;


22.
对于多张大数据量(这里几百条就算大了)的表JOIN,要先分页再JOIN,否则逻辑读会很高,性能很差。

23.select count(*) from table
;这样不带任何条件的count会引起全表扫描,并且没有任何业务意义,是一定要杜绝的。


24.
索引并不是越多越好,索引固然可以提高相应的select的效率,但同时也降低了insertupdate的效率,因为insertupdate时有可能会重建索引,所以怎样建索引需要慎重考虑,视具体情况而定。一个表的索引数最好不要超过6个,若太多则应考虑一些不常使用到的列上建的索引是否有必要。

25.
应尽可能的避免更新clustered索引数据列,因为clustered索引数据列的顺序就是表记录的物理存储顺序,一旦该列值改变将导致整个表记录的顺序的调整,会耗费相当大的资源。若应用系统需要频繁更新clustered索引数据列,那么需要考虑是否应将该索引建为clustered索引。

26.
尽量使用数字型字段,若只含数值信息的字段尽量不要设计为字符型,这会降低查询和连接的性能,并会增加存储开销。这是因为引擎在处理查询和连接时会逐个比较字符串中每一个字符,而对于数字型而言只需要比较一次就够了。

27.
尽可能的使用varchar/nvarchar代替char/nchar ,因为首先变长字段存储空间小,可以节省存储空间,其次对于查询来说,在一个相对较小的字段内搜索效率显然要高些。

28.
任何地方都不要使用select * from t,用具体的字段列表代替“*”,不要返回用不到的任何字段。

29.
尽量使用表变量来代替临时表。如果表变量包含大量数据,请注意索引非常有限(只有主键索引)。

30.
避免频繁创建和删除临时表,以减少系统表资源的消耗。临时表并不是不可使用,适当地使用它们可以使某些例程更有效,例如,当需要重复引用大型表或常用表中的某个数据集时。但是,对于一次性事件,最好使用导出表。

31.
在新建临时表时,如果一次性插入数据量很大,那么可以使用select into代替create table,避免造成大量log,以提高速度;如果数据量不大,为了缓和系统表的资源,应先createtable,然后insert

32.
如果使用到了临时表,在存储过程的最后务必将所有的临时表显式删除,先truncate table,然后drop table,这样可以避免系统表的较长时间锁定。

33.
尽量避免使用游标,因为游标的效率较差,如果游标操作的数据超过1万行,那么就应该考虑改写。

34.
使用基于游标的方法或临时表方法之前,应先寻找基于集的解决方案来解决问题,基于集的方法通常更有效。

35.
与临时表一样,游标并不是不可使用。对小型数据集使用FAST_FORWARD游标通常要优于其他逐行处理方法,尤其是在必须引用几个表才能获得所需的数据时。在结果集中包括合计的例程通常要比使用游标执行的速度快。如果开发时间允许,基于游标的方法和基于集的方法都可以尝试一下,看哪一种方法的效果更好。

36.
在所有的存储过程和触发器的开始处设置SET NOCOUNT ON,在结束时设置SET NOCOUNT OFF。无需在执行存储过程和触发器的每个语句后向客户端发送DONE_IN_PROC消息。

37.
尽量避免大事务操作,提高系统并发能力。

38.
尽量避免向客户端返回大数据量,若数据量过大,应该考虑相应需求是否合理。

 


参考文献:

http://www.cnblogs.com/exe19/p/5786806.html

http://database.51cto.com/art/200904/118526.htm

http://www.cnblogs.com/lddbupt/p/5781831.html

http://blog.csdn.net/u011225629/article/details/50492403/

原创粉丝点击