SQl语句查询性能优化

时间:2022-06-20 16:22:13
【摘要】本文从DBMS的查询优化器对SQL查询语句进行性能优化的角度出发,结合数据库理论,从查询表达式及其多种查询条件组合对数据库查询性能优化进行分析,总结出多种提高数据库查询性能优化策略,介绍索引的合理建立和使用以及高质量SQL查询语句的书写原则,从而实现高效的查询,提高系统的可用性。 
【关键词】SQL查询语句,索引,性能优化 

1.引言

在应用系统开发初期,由于开发数据库数据比较少,对于查询SQL语句,索引的运用与复杂视图的编写等体会不出SQL语句各种写法的性能优劣,但是应用系统实际应用后,随着数据库中数据的增加,系统的响应速度就成为目前系统需要解决的最主要的问题之一。系统优化中一个很重要的方面就是SQL语句的优化。对于海量数据,劣质SQL语句和优质SQL语句之间的速度差别可以达到上百倍,可见对于一个系统不是简单地能实现其功能就可,而是要写出高质量的SQL语句,提高系统的可用性。 面对海量数据查询,分时段对大批量数据进行删除、更新和插入操作,抓住需要优化的主要方面,针对不同的情况从如何采用高效的SQL入手来进行。

2.索引的正确使用 在海量数据表中,基本每个表都有一个或多个的索引来保证高效的查询,索引的使用需要遵循以下使用原则:

(1) 当插入的数据为数据表中的记录数量10%以上时, 首先需要删除该表的索引来提高数据的插入效率,当数据全部插入后再建立索引。 
(2) 避免在索引列上使用函数或计算,在WHERE子句中,如果索引列是函数的一部分,优化器将不使用索引而使用全表扫描。举例:     
    wk_ad_begin({pid : 21});wk_ad_after(21, function(){$('.ad-hidden').hide();}, function(){$('.ad-hidden').show();});
低效: select  *  from  table  where  salary * 12  >  25000;  

高效: select  *  from  table  where  salary  >  25000/12; 

低效: select * from table1 where name='zhangsan'  and  tID > 10000 

高效: select * from table1 where tID > 10000 and name='zhangsan'
如果tID是一个聚合索引,那么后一句仅仅从表的10000条以后的记录中查找就行了;而前一句则要先从全表中查找看有几个name='zhangsan'的,而后再根据限制条件条件tID>10000来提出查询结果。 
(3) 避免在索引列上使用NOT和”!=”或<> , 索引只能告诉什么存在于表中, 而不能告诉什么不存在于表中,当数据库遇到NOT和”!=”时,就会停止使用索引转而执行全表扫描。 
(4) 索引列上用>=替代>
高效:   select  *  from  table  where  Deptno >=4  
低效: select * from table where Deptno >3
两者的区别在于, 前者table将直接跳到第一个Deptno等于4的记录而后者将首先定位到Deptno=3的记录并且向前扫描到第一个Deptno大于3的记录。 
(5) 函数的列启用索引方法,如果一定要对使用函数的列启用索引,Oracle9i以上版本新的功能:基于函数的索引(Function-Based Index)是一个较好的方案,但该类型索引的缺点是只能针对某个函数来建立和使用该函数。 
create  index  EMP_I  ON  EMP (upper( ename)); /*建立基于函数的索引*/  

select  *  from EMP  where  upper( ename) = „BLACKSNAIL‟; /*将使用索引*/ 

3.  SQL语句性能优化

 3.1 WHERE子句中的连接顺序 
ORACLE采用自下而上的顺序解析where子句,根据这个原理,表之间的连接必须写在其它where条件之前,那些可以过滤掉最大数量记录的条件必须写在where子句的末尾。 
低效:Select  *  from  table where Salary > 50000  and Job = ‘MANAGER’ and 25 < (Select count(*) from table  where Mgr=table.Empno);  

高效:Select  *  from  table where 25 < (select count(*) from table   where Mgr=table.Empno)  and Salary> 50000 and Job = ‘MANAGER’;
3.2 用EXISTS替代IN 
在许多基于基础表的查询中,为了满足一个条件往往需要对另一个表进行联接,例如在ETL过程写数据到模型时经常需要关联10个左右的维表,从ORACLE执行的步骤来分析用IN的SQL与不用IN的SQL有以下区别:ORACLE试图将其转换成多个表的连接,如果转换不成功则先执行IN里面的子查询,再查询外层的表记录,如果转换成功则直接采用多个表的连接方式查询。由此可见用IN的SQL至少多了一个转换的过程, 在这种情况下,使用EXISTS而不用IN将提高查询的效率。
3.3 用NOT EXISTS替代NOT IN 
子查询中,NOT IN子句将执行一个内部的排序和合并,无论在哪种情况下,NOT IN都是最低效的,因为它对子查询中的表执行了一个全表遍历。用NOT EXISTS替代NOT IN将提高查询的效率。
3.4  !=或<> 操作符(不等于)
不等于操作符是永远不会用到索引的,因此对它的处理只会产生全表扫描。推荐方案:用其它相同功能的操作运算代替,如a<>0 改为 a>0 or a<0 。 
3.5  IS NULL 或IS NOT NULL操作(判断字段是否为空) 
判断字段是否为空一般是不会应用索引的,因为B树索引是不索引空值的。推荐方案:用其它相同功能的操作运算代替,如a is not null 改为 a>0 或a>’’等。不允许字段为空,而用一个缺省值代替空值。 
3.6  > 及 < 操作符(大于或小于操作符) 
大于或小于操作符一般情况下是不用调整的,因为它有索引就会采用索引查找,但有的情况下可以对它进行优化,如一个表有100万记录,一个数值型字段A,30万记录的A=0,30万记录的A=1,39万记录的A=2,1万记录的A=3。那么执行A>2与A>=3的效果就有很大的区别了,因为A>2时ORACLE会先找出为2的记录索引再进行比较,而A>=3时ORACLE则直接找到=3的记录索引。
3.7 优化GROUP BY 
提高GROUP BY 语句的效率,可以通过将不需要的记录在GROUP BY 之前过滤掉。  
低效: Select  Job, Avg(Salary)  from  table  group  by  Job  having  Job = ‘PRESIDENT’ OR  Job  = ‘MANAGER’
高效: Select Job, Avg(Salary) from table Where Job = ‘PRESIDENT’ OR Job = ‘MANAGER’ group by Job
3.8  LIKE操作符 
LIKE操作符可以应用通配符查询,里面的通配符组合可能达到几乎是任意的查询,但是如果用得不好则会产生性能上的问题,如LIKE ‘%5400%’ 这种查询不会引用索引,而LIKE ‘X5400%’则会引用范围索引。一个实际例子:用Students表中学生编号后面的标识号可来查询学生 Sno LIKE ‘%5400%’ 这个条件会产生全表扫描,如果改成Sno LIKE ’X5400%’ OR  Sno LIKE ’B5400%’ 则会利用Sno的索引进行两个范围的查询,性能肯定大大提高。 
3.9 有条件的使用UNION-ALL 替换UNION 
针对多表连接操作的情况很多,有条件的使用UNION-ALL 替换UNION的前提是:所连接的各个表中无主关键字相同的记录,因为UNION ALL 将重复输出两个结果集合中相同记录。UNION在进行表链接后会筛选掉重复的记录,所以在表链接后会对所产生的结果集进行排序运算,删除重复的记录再返回结果。当SQL语句需要UNION两个查询结果集合时,这两个结果集合会以UNION-ALL的方式被合并,然后在输出最终结果前进行排序。如果用UNION ALL替代UNION, 这样排序就不是必要了,效率就会因此得到提高3-5倍。
3.10 避免where 子句中对字段进行表达式操作或对字段进行函数操作 
因为若where 子句中如果索引列是表达式或函数的一部分,优化器就不能使用分布统计信息,这将导致引擎放弃使用索引而进行全表扫描。 
低效:Select  name from table  where  substring (id, 1, 1) = 'C' 

低效:Select  name from table  where  salary * 10 > 

4.其它的优化方法

数据库的查询优化方法不仅仅是索引和SQL语句的优化,其他方法的合理使用同样也能很好的对数据库查询功能起到优化作用。我们就来列举几种简单实用的方法。
4.1 使用临时表加速查询 
   创建临时表在一些情况下可以避免多重排序操作。但所创建的临时表的行要比主表的行少,其物理顺序就是所要求的顺序,这样就减少了输入和输出,降低了查询的工作量,提高了查询效率,而且临时表的创建并不会反映主表的修改。  
   比如:如果直接在存储上万条数据的永久表上重复循环进行统计、查询,其执行效率非常低,但是,先从存储大量数据的永久表中提取符和条件的存放到临时表后,在临时表上执行操作,效率会大大提高。
4.2 避免对大型表 行数据的顺序存取 
在嵌套查询中,对表的顺序存取对查询效率可能产生致命的影响。 
比如采用顺序存取策略,一个嵌套3层的查询,如果每层都查询1000行,那么这个查询就要查询10亿行数据。
避免这种情况的主要方法就是对连接的列进行索引。 两个表:学生表(学号、姓名、年龄……)       选课表(学号、课程号、成绩) 
如果两个表要做连接,就要在“学号”这个连接字段上建立索引。 
4.3  避免或简化排序  
应当简化或避免对大型表进行重复的排序。当能够利用索引自动以适当的次序产生输出时,优化器就避免了排序的步骤。以下是一些影响因素:
4.1.索引中不包括一个或几个待排序的列
4.2. group by或order by子句中列的次序与索引的次序不一样 
4.3. 排序的列来自不同的表 为了避免不必要的排序,就要正确地增建索引,合理地合并数据库表(尽管有时可能影响表的规范化,但相对于效率的提高是值得的)。如果排序不可避免,那么应当试图简化它,如缩小排序的列的范围等。
4.4 避免相关子查询如果在主查询和where子句中的查询中同时出现了一个列的标签,这样就会使主查询的列值改变后,子查询也必须重新进行一次查询。因为查询的嵌套层次越多,查询的效率就会降低,所以我们应当避免子查询。如果无法避免,就要在查询的过程中过滤掉尽可能多的。 

4.5 用排序来取代非顺序存取 非顺序磁盘存取是最慢的操作,表现在磁盘存取臂的来回移动。SQL语句隐藏了这一情况,使得我们在写应用程序时很容易写出要求存取大量非顺序页的查询。 有些时候,用数据库的排序能力来替代非顺序的存取能改进查询。 

5. 总结 
对于海量数据的查询优化,我们要抓住关键问题,写出高质量的SQL语句,提高系统的可用性。在计算实际的查询处理代价时,必须考虑优化本身的时间和空间开销。如果选择最佳的处理方案的开销太大,则可能得不偿失。实际上,查询优化在合理的优化处理开销下,寻找较好的处理方案,而不一定是最佳方案