SQL优化

来源:互联网 发布:安卓怎么伪造mac 编辑:程序博客网 时间:2024/06/07 03:58

1.说明:
    数据库系统需要保存大量历史记录,系统内存在许多历史记录表,因此常常出现系统运行一段时间,表记录数达到一定数量后,系统响应明显变慢的现象。为尽可能的提高SQL执行的效率,我们在编写SQL语句应该遵循一定的优化规则,使代码风格统一、规范。充分利用表索引,避免进行全表扫描;充分利用结构化编程方式,提高查询的复用能力,也许完全遵守以下方法速度未必达到想要的结果,但是养成一个好的编程习惯是重要的。

2.调尤方法

2.1. 相同功能、性能的SQL语句

   ORACLE采用共享内存SGA的机制,因此ORACLE会对每个不同写法的SQL进行分析,并且占用共享内存;如果书写格式完全相同,则ORACLE只分析一次,遇到相同书写格式的SQL,会直接从共享内存中获取结果集;这样便能减少共享池的开销以及代码的复用。
处理方法:保证书写格式相同,包括大小写,空格位置,表别名等一致;将一些通用的SQL语句作为公共函数由其它函数调用的方式使用。
2.2. 动态SQL:动态SQL采用变量动态绑定的方式,避免重复解析。2.3.连接方式与表名顺序
多表查询时需要选择最有效率的表名顺序(基于规则的优化器有效),ORACLE的解析器按照从右到左的顺序处理FROM子句中的表名;因此写在最后的那张基础表最先被处理,即选择记录数最少的表作为基础表,首先,扫描FROM子句中最右的那个表,并对记录进行排序,然后扫描FROM子句中最后第二个表,最后将所有从第二个表中检索出的记录与第一个表中合适记录进行合并。
2.4.查询条件顺序
ORACLE采用自下而上的顺序解析WHERE子句,根据这个原理,表之间的连接必须写在其他WHERE条件之前, 那些可以过滤掉最大数量记录的条件适合写在WHERE子句的末尾。
2.5. 语法和语义2.5.1.SELECT子句中避免使用' * '
使用'*' ,Oracle便会查询数据字典,依次解析各列名,应明确指定各列的名称,这样也便于理解。
Select * from t_user t;
2.5.2. 使用表的别名(Alias)
多表查询时,使用表的别名,同样可以避免解析,避免歧义,提高效率。
2.5.3. 常量优化:
常量的计算是在语句被优化时一次性完成,而不是在每次执行时。下面是检索月薪大于2000的的表达式:
sal > 24000/12
sal > 2000
sal*12 > 24000
如果SQL语句包括第一种情况,优化器会简单地把它转变成第二种。
优化器不会简化跨越比较符的表达式,例如第三条语句,鉴于此,应尽量写用常量跟字段比较检索的表达式,而不要将字段置于表达式当中。否则没有办法优化,比如如果sal上有索引,第一和第二就可以使用,第三就难以使用。
2.6. 函数和表达式2.6.1.>=替代>
例如DEPTNO上有一个索引,
高效: SELECT * FROM EMP WHERE DEPTNO >=4
低效:SELECT * FROM EMP WHERE DEPTNO >3
   两者的区别在于,前者DBMS将直接跳到第一个DEPT等于4的记录而后者将首先定位到DEPTNO=3的记录并且向前扫描到第一个DEPT大于3的记录
2.6.2. 使用DECODE函数来减少处理时间
    使用DECODE函数可以避免重复扫描相同记录或重复连接相同的表.
2.6.3. TRUNCATE替代DELETE
    当删除表中的记录时,在通常情况下, 回滚段(rollback segments )用来存放可以被恢复的信息.如果你没有COMMIT事务,ORACLE会将数据恢复到删除之前的状态(准确地说是恢复到执行删除命令之前的状况),而当运用TRUNCATE时, 回滚段不再存放任何可被恢复的信息.当命令运行后,数据不能被恢复,因此很少的资源被调用,执行时间也会很短。 TRUNCATE只在删除全表或分区适用,TRUNCATEDDL不是DML
2.7. 常用操作符优化
2.7.1. LIKE操作符组合没有通配符的表达式
优化器把使用LIKE操作符和一个没有通配符的表达式组成的检索表达式转换为一个“=”操作符表达式。
例如:优化器会把表达式ename LIKE 'SMITH'转换为ename = 'SMITH'
优化器只能转换涉及到可变长数据类型的表达式,前一个例子中,如果ENAME字段的类型是CHAR(10), 那么优化器将不做任何转换。
一般来讲LIKE比较难以优化。
2.7.2. EXISTS替代INnot exists 替换 not in
    优化器把使用IN比较符的检索表达式替换为等价的使用“=”和“OR”操作符的检索表达式。 用IN/ not in将启用全表扫描,使用EXISTS将利用索引,提高查询效率。EXISTS要远比IN的效率高。里面关系到full table scanrange scan。几乎将所有的IN操作符子查询改写为使用EXISTS的子查询。
    例如,优化器会把表达式ename IN ('SMITH','KING','JONES')替换为
ename = 'SMITH' OR ename = 'KING' OR ename = 'JONES
语句1
SELECT dname, deptno FROM dept
WHERE deptno NOT IN
(SELECT deptno FROM emp);
语句2
SELECT dname, deptno FROM dept
WHERE NOT EXISTS
(SELECT deptno FROM emp
WHERE dept.deptno = emp.deptno);
2要比1的执行性能好很多。因为1中对emp进行了full table scan,这是很浪费时间的操作。而且1中没有用到empINdex因为没有WHERE子句。而2中的语句对emp进行的是range scan
2.7.3. 用表连接替换EXISTS2.7.4.EXISTS替换DISTINCT2.7.5.避免在索引列上使用IS NULLIS NOT NULL
不能用null作索引,任何包含null值的列都将不会被包含在索引中。即使索引有多列这样的情况下,只要这些列中有一列含有null,该列就会从索引中排除。也就是说如果某列存在空值,即使对该列建索引也不会提高性能。任何在WHERE子句中使用is nullis not null的语句优化器是不允许使用索引的。
举例如下:
低效 (索引失效) :
SELECT * FROM DEPARTMENT WHERE DEPT_CODE IS NOT NULL;
高效(索引有效) : SELECT * FROM DEPARTMENT WHERE DEPT_CODE >=0;
2.7.6. UNION-ALL替换UNION
    当SQL语句需要UNION两个查询结果集合时,这两个结果集合会以UNION-ALL的方式被合并,然后在输出最终结果前进行排序。
    如果用UNION ALL替代UNION,这样排序就不是必要了。效率就会因此得到提高。
2.7.7. UNION替换OR (适用于索引列)
    如果不产生大量重复值,可以考虑把子句拆开。拆开的子句中应该包含索引。
通常情况下,用UNION替换WHERE子句中的OR将会起到较好的效果。对索引列使用OR将造成全表扫描。注意,以上规则只针对多个索引列有效。如果有column没有被索引,查询效率可能会因为你没有选择OR而降低。
2.7.8. WHERE替代ORDER BY
ORDER BY 子句只在两种严格的条件下使用索引: ORDER BY中所有的列必须包含在相同的索引中并保持在索引中的排列顺序。ORDER BY中所有的列必须定义为非空。WHERE子句使用的索引和ORDER BY子句中所使用的索引不能并列.
2.7.9. WHERE子句替换HAVING子句
避免使用HAVING子句,HAVING只会在检索出所有记录之后才对结果集进行过滤。 这个处理需要排序,总计等操作。如果能通过WHERE子句限制记录的数目,那就能减少这方面的开销。例子如下:
  低效:
  SELECT REGIONAVG(LOG_SIZE)
   FROM LOCATION
   GROUP BY REGION
    HAVING REGION REGION != 'SYDNEY'   AND REGION != 'PERTH'
    高效
    SELECT REGIONAVG(LOG_SIZE)
    FROM LOCATION
    WHERE REGION REGION != 'SYDNEY'
    AND REGION != 'PERTH'
    GROUP BY REGION
HAVING 中的条件一般用于对一些集合函数的比较,如COUNT()等等。除此之外,一般的条件应该写在WHERE子句中。
2.7.10. GROUP BY的使用
提高GROUP BY 语句的效率,可以通过将不需要的记录在GROUP BY之前过滤掉。下面两个查询返回相同结果但第二个明显就快了许多。
2.7.11. BETWEEN操作符:
    优化器总是用“>=”和“<=”比较符来等价的代替BETWEEN操作符。
例如:优化器会把表达式sal BETWEEN 2000 AND 3000sal >= 2000 AND sal <= 3000来代替。
2.7.12. NOT操作符:
    优化器总是试图简化检索条件以消除“NOT”逻辑操作符的影响,这将涉及到“NOT”操作符的消除以及代以相应的比较运算符。
    例如,优化器将下面的第一条语句用第二条语句代替:
    NOT deptno = (SELECT deptno FROM emp WHERE ename = 'TAYLOR')
    deptno <> (SELECT deptno FROM emp WHERE ename = 'TAYLOR')
    通常情况下一个含有NOT操作符的语句有很多不同的写法,优化器的转换原则是使“NOT”操作符后边的子句尽可能的简单,即使可能会使结果表达式包含了更多的“NOT”操作符。
    例如,优化器将如下所示的第一条语句用第二条语句代替:
    NOT (sal < 1000 OR comm IS NULL)
    NOT sal < 1000 AND comm IS NOT NULL sal >= 1000 AND comm IS NOT NULL
2.7.13. ANYSOME操作符优化:
    优化器将跟随值列表的ANYSOME检索条件用等价的同等操作符和“OR”组成的表达式替换。
    例如,优化器将如下所示的第一条语句用第二条语句替换:
    sal > ANY (:first_sal, :second_sal)
    sal > :first_sal OR sal > :second_sal
    优化器将跟随子查询的ANYSOME检索条件转换成由“EXISTS”和一个相应的子查询组成的检索表达式。
    例如,优化器将如下所示的第一条语句用第二条语句替换:
    x > ANY (SELECT sal FROM emp WHERE job = 'ANALYST')
EXISTS (SELECT sal FROM emp WHERE job = 'ANALYST' AND x > sal)
2.7.14. ALL操作符优化:
    优化器将跟随值列表的ALL操作符用等价的“=”和“AND”组成的表达式替换。例如:
    sal > ALL (:first_sal, :second_sal)表达式会被替换为:
    sal > :first_sal AND sal > :second_sal
    对于跟随子查询的ALL表达式,优化器用ANY和另外一个合适的比较符组成的表达式替换。例如
    x > ALL (SELECT sal FROM emp WHERE deptno = 10) 替换为:
    NOT (x <= ANY (SELECT sal FROM emp WHERE deptno = 10))
    接下来优化器会把第二个表达式适用ANY表达式的转换规则转换为下面的表达式:
    NOT EXISTS (SELECT sal FROM emp WHERE deptno = 10 AND x <= sal)
2.7.15. 全局参数2.8.其它因素2.8.1.减少访问数据库的次数
当执行每条SQL语句时,ORACLE在内部执行了许多工作:
解析SQL语句
估算索引的利用率
绑定变量
读数据块等等
由此可见, 减少访问数据库的次数, 就能实际上减少ORACLE的工作量。
2.8.2. 常使用COMMIT
只要有可能,在程序中尽量多使用COMMIT,这样程序的性能得到提高,需求也会因为COMMIT所释放的资源而减少。COMMIT所释放的资源:
1回滚段上用于恢复数据的信息.
2被程序语句获得的锁
3 redo log buffer中的空间
4 ORACLE为管理上述3种资源中的内部花费
2.8.3. 避免不必要的子查询
一个列的标签同时在主查询和WHERE子句中的查询中出现,那么很可能当主查询中的列值改变之后,子查询必须重新查询一次。查询嵌套层次越多,效率越低,因此应当尽量避免子查询。如果子查询不可避免,那么要在子查询中过滤掉尽可能多的行。
Select u.* from t_user u where u.areaid in (select a.id from t_area a where ja.name like ‘苏州%’)
Select u.* from t_user u, (select a.* from t_area a where a.name like ‘苏州%‘) a
  Where u.areaid = a.id
2.8.4. 合理的索引设计2.8.4.1.避免或简化排序:
应当简化或避免对大型表进行重复的排序。当能够利用索引自动以适当的次序产生输出时,优化器就避免了排序的步骤。以下是一些影响因素:
索引中不包括一个或几个待排序的列;
group byorder by子句中列的次序与索引的次序不一样;
排序的列来自不同的表。
为了避免不必要的排序,就要正确地增建索引,合理地合并数据库表(尽管有时可能影响表的规范化,但相对于效率的提高是值得的)。如果排序不可避免,那么应当试图简化它,如缩小排序的列的范围等。
2.8.4.2. 消除对大型表行数据的顺序存取
在嵌套查询中,对表的顺序存取对查询效率可能产生致命的影响。比如采用顺序存取策略,一个嵌套3层的查询,如果每层都查询1000行,那么这个查询就要查询10亿行数据。避免这种情况的主要方法就是对连接的列进行索引。例如,两个表:学生表(学号、姓名、年龄??)和选课表(学号、课程号、成绩)。如果两个表要做连接,就要在“学号”这个连接字段上建立索引。
还可以使用并集来避免顺序存取。尽管在所有的检查列上都有索引,但某些形式的WHERE子句强迫优化器使用顺序存取。下面的查询将强迫对orders表执行顺序操作:
SELECT FROM orders WHERE (customer_num=104 AND order_num>1001) OR order_num=1008
虽然在customer_numorder_num上建有索引,但是在上面的语句中优化器还是使用顺序存取路径扫描整个表。因为这个语句要检索的是分离的行的集合,所以应该改为如下语句:
SELECT FROM orders WHERE customer_num=104 AND order_num>1001
UNION
SELECT FROM orders WHERE order_num=1008
这样就能利用索引路径处理查询。
2.8.4.3. 聚集索引和非聚集索引
    非聚集索引和聚集索引最大的区别在于,他针对数据的索引列生成了一个已经进行了排序的索引页。而他本身的数据在物理存储上是没有顺序可言的。
也因为这样的结构的不同,所以聚集索引和非聚集索引有一个增加、修改、删除时候的效率比较。
2.8.4.3.1. 聚集索引使用注意事项
     定义聚集索引键时使用的列越少越好。
     • 包含大量非重复值的列。
    .• 使用下列运算符返回一个范围值的查询:BETWEEN>>=<<=
     •  被连续访问的列。
     •  回大型结果集的查询。
     • 经常被使用联接或 GROUP BY子句的查询访问的列;一般来说,这些是外键列。对 ORDER BYGROUP BY子句中指定的列进行索引,可以使 SQL Server不必对数据进行排序,因为这些行已经排序。这样可以提高查询性能。
    •  OLTP 类型的应用程序,这些程序要求进行非常快速的单行查找(一般通过主键)。应在主键上创建聚集索引。
2.8.4.3.2. 聚集索引不适用于:
     • 频繁更改的列 。这将导致整行移动(因为 SQL Server 必须按物理顺序保留行中的数据值)。这一点要特别注意,因为在大数据量事务处理系统中数据是易失的。如果你插入的数据在已经有的数据的中间,那么他会引起一个分页这样的一个操作,这样会让你的操作更消耗一些。
     • 宽键 。来自聚集索引的键值由所有非聚集索引作为查找键使用,因此存储在每个非聚集索引的叶条目内。
非聚集索引:数据存储在一个地方,索引存储在另一个地方,索引带有指针指向数据的存储位置。
2.8.4.3.3. 聚集索引
      非聚集索引中的项目按索引键值的顺序存储,而表中的信息按另一种顺序存储(这可以由聚集索引规定)。对于非聚集索引,可以为在表非聚集索引中查找数据时常用的每个列创建一个非聚集索引。有些书籍包含多个索引。例如,一本介绍园艺的书可能会包含一个植物通俗名称索引,和一个植物学名索引,因为这是读者查找信息的两种最常用的方法。
2.8.4.3.4. 举例
例:表card7896行,在card_no上有一个非聚集索引,表account191122行,在account_no上有一个非聚集索引,试看在不同的表连接条件下,两个SQL的执行情况:
SELECT sum(a.amount) FROM account a,card b WHERE a.card_no = b.card_no
20秒)
SQL改为:
SELECT sum(a.amount) FROM account a,card b WHERE a.card_no = b.card_no and a.account_no=b.account_no
< 1秒)
分析:
在第一个连接条件下,最佳查询方案是将account作外层表,card作内层表,利用card上的索引,其I/O次数可由以下公式估算为:
外层表account上的22541+(外层表account191122*内层表card上对应外层表第一行所要查找的3页)=595907I/O
在第二个连接条件下,最佳查询方案是将card作外层表,account作内层表,利用account上的索引,其I/O次数可由以下公式估算为:
外层表card上的1944+(外层表card7896*内层表account上对应外层表每一行所要查找的4页)= 33528I/O
可见,只有充份的连接条件,真正的最佳方案才会被执行。
多表操作在被实际执行前,查询优化器会根据连接条件,列出几组可能的连接方案并从中找出系统开销最小的最佳方案。连接条件要充份考虑带有索引的表、行数多的表;内外表的选择可由公式:外层表中的匹配行数*内层表中每一次查找的次数确定,乘积最小为最佳方案。
不可优化的WHERE子句
1
下列SQL条件语句中的列都建有恰当的索引,但执行速度却非常慢:
SELECT * FROM record WHERE substrINg(card_no,1,4)='5378'
(13)
SELECT * FROM record WHERE amount/30< 1000
11秒)
SELECT * FROM record WHERE convert(char(10),date,112)='19991201'
10秒)
分析:
WHERE子句中对列的任何操作结果都是在SQL运行时逐列计算得到的,因此它不得不进行表搜索,而没有使用该列上面的索引;如果这些结果在查询编译时就能得到,那么就可以被SQL优化器优化,使用索引,避免表搜索,因此将SQL重写成下面这样:
SELECT * FROM record WHERE card_no like '5378%'
< 1秒)
SELECT * FROM record WHERE amount< 1000*30
< 1秒)
SELECT * FROM record WHERE date= '1999/12/01'
< 1秒)
2.8.4.4. 实例
例:表record620000行,试看在不同的索引下,下面几个SQL的运行情况:
语句A
SELECT count(*) FROM record
WHERE date >'19991201' and date < '19991214and amount >2000
语句B
SELECT count(*) FROM record
WHERE date >'19990901' and place IN ('BJ','SH')
语句C
SELECT date,sum(amount) FROM record
group by date
1 date上建有一个非聚集索引
A(25)
B(27)
C(55)
分析:
date上有大量的重复值,在非聚集索引下,数据在物理上随机存放在数据页上,在范围查找时,必须执行一次表扫描才能找到这一范围内的全部行。
2 date上的一个聚集索引
A:(14秒)
B:(14秒)
C:(28秒)
分析:
在聚集索引下,数据在物理上按顺序在数据页上,重复值也排列在一起,因而在范围查找时,可以先找到这个范围的起末点,且只在这个范围内扫描数据页,避免了大范围扫描,提高了查询速度。
3 placedateamount上的组合索引
A:(26秒)
C:(27秒)
B:(< 1秒)
分析:
这是一个不很合理的组合索引,因为它的前导列是place,第一和第二条SQL没有引用place,因此也没有利用上索引;第三个SQL使用了place,且引用的所有列都包含在组合索引中,形成了索引覆盖,所以它的速度是非常快的。
4 dateplaceamount上的组合索引
A(< 1)
B:(< 1秒)
C:(11秒)
分析:
这是一个合理的组合索引。它将date作为前导列,使每个SQL都可以利用索引,并且在第一和第三个SQL中形成了索引覆盖,因而性能达到了最优。
总结1
缺省情况下建立的索引是非聚集索引,但有时它并不是最佳的;合理的索引设计要建立在对各种查询的分析和预测上。一般来说:
有大量重复值、且经常有范围查询(between, >,<>=,< =)和order bygroup by发生的列,考虑建立聚集索引;
经常同时存取多列,且每列都含有重复值可考虑建立组合索引;在条件表达式中经常用到的不同值较多的列上建立检索,在不同值少的列上不要建立索引。比如在雇员表的“性别”列上只有“男”与“女”两个不同值,因此就无必要建立索引。如果建立索引不但不会提高查询效率,反而会严重降低更新速度。
组合索引要尽量使关键查询形成索引覆盖,其前导列一定是使用最频繁的列。
2.8.5. 避免使用不兼容的数据类型:
    例如float和int、char和varchar、binary和varbinary是不兼容的。数据类型的不兼容可能使优化器无法执行一些本来可以进行的优化操作。例如:
SELECT name FROM employee WHERE salary > 60000
在这条语句中,如salary字段是money型的,则优化器很难对其进行优化,因为60000是个整型数。我们应当在编程时将整型转化成为钱币型,而不要等到运行时转化。

2.8.6. 存储过程或Dao层中,采用临时表优化查询:


1.从parven表中按vendor_num的次序读数据:
SELECT part_num,vendor_num,price FROM parven ORDER BY vendor_num
INTO temp pv_by_vn
这个语句顺序读parven(50页),写一个临时表(50页),并排序。假定排序的开销为200页,总共是300页。
2.把临时表和vendor表连接,把结果输出到一个临时表,并按part_num排序:
SELECT pv_by_vn,* vendor.vendor_num FROM pv_by_vn,vendor
WHERE pv_by_vn.vendor_num=vendor.vendor_num
ORDER BY pv_by_vn.part_num
INTO TMP pvvn_by_pn
DROP TABLE pv_by_vn
这个查询读取pv_by_vn(50页),它通过索引存取vendor表1.5万次,但由于按vendor_num次序排列,实际上只是通过索引顺序地读 vendor表(40+2=42页),输出的表每页约95行,共160页。写并存取这些页引发5*160=800次的读写,索引共读写892页。
3.把输出和part连接得到最后的结果:
SELECT pvvn_by_pn.*,part.part_desc FROM pvvn_by_pn,part
WHERE pvvn_by_pn.part_num=part.part_num
DROP TABLE pvvn_by_pn
这样,查询顺序地读pvvn_by_pn(160页),通过索引读part表1.5万次,由于建有索引,所以实际上进行1772次磁盘读写,优化比例为30∶1。

2.9. 常见优化工具介绍

2.9.1. 用Explain Plan分析SQL语句
EXPLAIN PLAN 是一个很好的分析SQL语句的工具, 它甚至可以在不执行SQL的情况下分析语句。通过分析, 我们就可以知道ORACLE是怎么样连接表,使用什么方式扫描表(索引扫描或全表扫描)以及使用到的索引名称。
(2)  SQL PLUS的TRACE

3. 常用术语

u 全表扫描
全表扫描就是顺序地访问表中每条记录,ORACLE采用一次读入多个数据块(database block)的方式优化全表扫描
u 通过索引访问(ROWID )
ROWID包含了表中记录的物理位置信息,ORACLE采用索引实现了数据和存放数据的物理位置(ROWID)之间的联系,通常索引提供了快速访问ROWID的方法,因此那些基于索引列的查询就可以得到性能上的提高。
u 共享SQL
(1)Oracle提供对执行过的SQL语句进行高速缓冲的机制。被解析过并且确定了执行路径的SQL语句存放在SGA的共享池中。
(2)Oracle执行一个SQL语句之前每次先从SGA共享池中查找是否有缓冲的SQL语句,如果有则直接执行该SQL语句。
(3)可以通过适当调整SGA共享池大小来达到提高Oracle执行性能的目的。
u ORACLE优化器
(1)RULE(基于规则)
(2)COST(基于成本)
(3)CHOOSE(选择性)

0 0
原创粉丝点击