1.一个分页语句引发的思考
执行计划显示耗时是2.491秒,这是Oracle预估时间,实际在PL/SQL中的执行时间是大于20s。
2.优化分析过程
全表查询单列进行排序与对全列进行排序的效率对比
运用单列排序分页
3.Rownum和Rowid的区别
Rownum 是逻辑地址。表示查询某条记录在整个结果集中的位置,同一条记录查询条件不同对应的rownum是不同的而 rowid是不会变的。PL/SQL实际执行时间平均为1s左右
5.多表关联查询对比
收款单列表查询原SQL
SELECT *
FROM (SELECT A.*, ROWNUM R
FROM (select frs.*, fc.ERP_COMPANY_ID
from (SELECT FR.*
FROM FIN_RECEIPT FR
WHERE FR.IS_DELETE = 0
AND FR.VALID = 'T'
AND FR.STATUS = '20'
AND INSTR(FR.PAYER, '河北') > 0
order by FR.RECEIPT_DATE DESC) frs
LEFT JOIN FIN_COMPANY FC
ON frs.RECEIPTER_ID = fc.PKID
and fc.is_delete = 0) A
WHERE ROWNUM <= 1200) B
WHERE R > 0;
PL/SQL执行平均(连续执行取平均值)耗时:2.6s
收款单列表查询修改后SQL
SELECT t.*, fcom.ERP_COMPANY_ID
FROM fin_receipt t
LEFT JOIN FIN_COMPANY fcom
ON t.RECEIPTER_ID = fcom.PKID
where t.rowid in (select rid
from (SELECT A.rid, ROWNUM R
FROM (select frs.*,fc.ERP_COMPANY_ID
from (SELECT FR.rowid rid,
FR.RECEIPTER_ID,
FR.IS_DELETE,
FR.VALID,
FR.STATUS,
FR.RECEIPT_DATE,
FR.receipt_No,
FR.PAYER_ID,
FR.PAYER,
FR.BIZ_TYPE_ID,
FR.AVAILABLE_AMOUNT
FROM FIN_RECEIPT FR
where FR.IS_DELETE = 0
AND FR.VALID = 'T'
AND FR.STATUS = '20'
and FR.AVAILABLE_AMOUNT = 0
AND INSTR(FR.PAYER, '河北') > 0
order by FR.RECEIPT_DATE DESC) FRS
LEFT JOIN FIN_COMPANY FC
ON frs.RECEIPTER_ID = fc.PKID
and fc.is_delete = 0) A
WHERE ROWNUM <= 1200) B
where r > 0)
order by t.RECEIPT_DATE DESC;
执行计划运行结果
PL/SQL执行平均(连续执行取平均值)耗时:2.0s
SQL优化常用规则
(1)选择最有效率的表名顺序(只在基于规则的优化器中有效)。
ORACLE的解析器按照从右到左的顺序处理FROM子句中的表名,FROM子句中写在最后的表(基础表 driving table)将被最先处理,在FROM子句中包含多个表的情况下,你必须选择记录条数最少的表作为基础表。如果有3个以上的表连接查询,那就需要选择交叉表(intersection table)作为基础表,交叉表是指那个被其他表所引用的表。
(2)WHERE子句中的连接顺序。
ORACLE采用自下而上的顺序解析WHERE子句,根据这个原理,表之间的连接必须写在其他WHERE条件之前,那些可以过滤掉最大数量记录的条件必须写在WHERE子句的末尾。
(3)SELECT子句中避免使用 “*” 。
ORACLE在解析的过程中,会将“*”依次转换成所有的列名,这个工作是通过查询数据字典完成的,这意味着将耗费更多的时间。
(4)减少访问数据库的次数。
ORACLE在内部执行了许多工作:解析SQL语句,估算索引的利用率、绑定变量、读数据块等。
(5)通过内部函数提高SQL效率。
复杂的SQL往往牺牲了执行效率。能够掌握上面的运用函数解决问题的方法在实际工作中是非常有意义的。
(6)使用表的别名(Alias)。
当在SQL语句中连接多个表时,请使用表的别名并把别名前缀于每个Column上,这样一来,就可以减少解析的时间并减少那些由Column歧义引起的语法错误。
(7)用EXISTS替代IN、用NOT EXISTS替代NOT IN。
在许多基于基础表的查询中,为了满足一个条件,往往需要对另一个表进行联接。在这种情况下,使用EXISTS(或NOT EXISTS)通常将提高查询的效率。 在子查询中,NOT IN子句将执行一个内部的排序和合并。无论在哪种情况下,NOT IN都是最低效的 (因为它对子查询中的表执行了一个全表遍历)。为了避免使用NOT IN,我们可以把它改写成外连接(Outer Join)或NOT EXISTS。
例子:
(高效)
SELECT * FROM EMP (基础表)
WHERE EMPNO > 0 AND EXISTS (SELECT ‘X’
FROM DEPT
WHERE DEPT.DEPTNO = EMP.DEPTNO AND LOC = ‘MELB’)
(低效)
SELECT * FROM EMP (基础表)
WHERE EMPNO > 0 AND DEPTNO IN(SELECT DEPTNO FROM DEPT WHERE LOC = ‘MELB’)
(6)用索引提高效率。
索引是表的一个概念部分,用来提高检索数据的效率,ORACLE使用了一个复杂的自平衡B-tree结构。通常,通过索引查询数据比全表扫描要快。当ORACLE找出执行查询和Update语句的最佳路径时,ORACLE优化器将使用索引。
同样在联结多个表时使用索引也可以提高效率。另一个使用索引的好处是,它提供了主键(primarykey)的唯一性验证。那些LONG或LONG RAW数据类型,你可以索引几乎所有的列。通常,在大型表中使用索引特别有效。当然,你也会发现,在扫描小表时,使用索引同样能提高效率。
虽然使用索引能得到查询效率的提高,但是我们也必须注意到它的代价。索引需要空间来存储,也需要定期维护,每当有记录在表中增减或索引列被修改时,索引本身也会被修改。这意味着每条记录的INSERT、DELETE、UPDATE将为此多付出4、5次的磁盘I/O。因为索引需要额外的存储空间和处理,那些不必要的索引反而会使查询反应时间变慢。定期的重构索引是有必要的。
(7)用EXISTS替换DISTINCT:
当提交一个包含一对多表信息(比如部门表和雇员表)的查询时,避免在SELECT子句中使用DISTINCT。 一般可以考虑用EXIST替换,EXISTS 使查询更为迅速,因为RDBMS核心模块将在子查询的条件一旦满足后,立刻返回结果。
例子:
(低效):
SELECT DISTINCT DEPT_NO,DEPT_NAME
FROM DEPT D , EMP E
WHERE D.DEPT_NO = E.DEPT_NO
(高效):
SELECT DEPT_NO,DEPT_NAME FROM DEPT D
WHERE EXISTS ( SELECT ‘X'
FROM EMP E
WHERE E.DEPT_NO = D.DEPT_NO);
(8)避免使用耗费资源的操作。
带有DISTINCT、UNION、MINUS、INTERSECT、ORDER BY的SQL语句会启动SQL引擎执行耗费资源的排序(SORT)功能。DISTINCT需要一次排序操作,而其他的至少需要执行两次排序。通常,带有UNION、MINUS、INTERSECT的SQL语句都可以用其他方式重写。如果你的数据库的SORT_AREA_SIZE调配得好,使用UNION、MINUS、INTERSECT也是可以考虑的,毕竟它们的可读性很强。
(9)sql语句用大写的。
因为oracle总是先解析sql语句,把小写的字母转换成大写的再执行。
(10)在java代码中尽量少用连接符“+”连接字符串!
(11)避免在索引列上使用NOT。
我们要避免在索引列上使用NOT,NOT会产生和在索引列上使用函数相同的影响。当ORACLE遇到NOT时,会停止使用索引转而执行全表扫描。
(12)避免在索引列上使用计算。
WHERE子句中,如果索引列是函数的一部分。优化器将不使用索引而使用全表扫描。
例子:
低效:
SELECT … FROM DEPT WHERE SAL * 12 > 25000;
高效:
SELECT … FROM DEPT WHERE SAL > 25000/12;
(13)用>=替代>
例子:
高效:
SELECT * FROM EMP WHERE DEPTNO >=4
低效:
SELECT * FROM EMP WHERE DEPTNO >3
两者的区别在于, 前者DBMS将直接跳到第一个DEPT等于4的记录而后者将首先定位到DEPTNO=3的记录并且向前扫描到第一个DEPT大于3的记录.