人生倒计时
- 今日已经过去小时
- 这周已经过去天
- 本月已经过去天
- 今年已经过去个月
1. SQL 语句执行步骤
语法分析 > 语义分析 > 视图转换 > 表达式转换 > 选择优化器 > 选择连接方法 > 选择连接顺序 > 选择数据搜索路径 > 运行“执行计划”
2. 选择合适的优化器 RULE(基于规则)、COST(基于成本)、(可选)
3. 访问表的方法 全表扫描 全表扫描是按顺序访问表中的每条记录,通过一次读取多个数据块来优化全表扫描。通过ROWID访问表 ROWID包含了表中记录的物理位置信息,并通过索引实现了数据与存储数据的物理位置(ROWID)的连接。通常,索引提供了一种快速访问ROWID的方法,因此那些基于索引列的查询可以在性能上得到提升。
4. 共享 SQL 语句
5. 选择最高效的表名序列 示例:表 TAB1 16,384 条记录 表 TAB2 1 条记录
/*选择TAB2作为基础表 (最好的方法)*/
SELECT COUNT(*) FROM TAB1,TAB2
/*执行时间0.96秒*/
/*选择TAB1作为基础表 (不佳的方法)*/
SELECT COUNT(*) FROM TAB2,TAB1
/*执行时间26.09秒*/
如果要加入查询的表超过3张,则需要选择交叉表(表)作为基表,交叉表是指被其他表引用的表。
/*高效的SQL*/
SELECT * FROM LOCATION L, CATEGORY C, EMP E
WHERE E.EMP_NO BETWEEN 1000 AND 2000
AND E.CAT_NO = C.CAT_NO
AND E.LOCN = L.LOCN
会比下面的 SQL 更高效
/*低效的SQL*/
SELECT * FROM EMP E, LOCATION L, CATEGORY C
WHERE E.CAT_NO = C.CAT_NO
AND E.LOCN = L.LOCN
AND E.EMP_NO BETWEEN 1000 AND 2000
6. Where 子句中的连接顺序以自下而上或从右到左的顺序解析 WHERE 子句。根据这个原则,表之间的连接必须写在其他 WHERE 条件之前,而那些可以过滤掉最大记录数的条件必须写在 WHERE 子句的末尾。
/*低效,执行时间156.3秒*/
SELECT Column1,Column2
FROM EMP EWHERE E.SAL > 50000
AND E.JOB = 'MANAGER'
AND 25 <
(SELECT COUNT(*) FROM EMP
WHERE MGR = E.EMPNO)
/*高效,执行时间10.6秒*/
SELECT Column1,Column2FROM EMP E
WHERE 25 < (SELECT COUNT(*) FROM EMP
WHERE MGR=E.EMPNO)
AND E.SAL > 50000
AND E.JOB = 'MANAGER'
避免在 7. 子句中使用“*”
8. 减少数据库访问次数 在每条SQL语句执行的时候,内部都做了很多工作:解析SQL语句>估计索引的利用率>绑定变量>读取数据块等。可以是看到减少访问数据库的次数,实际上可以减少工作量。
9. 整个简单的无关数据库访问如果有几个简单的数据库查询语句,可以将它们组合成一个查询(即使它们之间没有关系),以减少过多的数据库IO开销。这种方法虽然提高了效率,但是程序的可读性大大降低,所以还是要权衡利弊。
10. 使用代替
11.尽可能多的使用尽可能多的在程序中使用,这样可以提高程序的性能,由于资源的释放而减少需求。发布的资源:
12. 统计记录数
Select count(*) from tablename;
Select count(1) from tablename;
Select count(column) from tablename;
一般认为,在没有主键索引的情况下,第二种 COUNT(1) 方法最快。如果只有一列,没有索引,COUNT(*) 更快。如果有索引列,当然,使用索引列 COUNT() 是最快的。
13. 将该子句替换为Where子句,避免该子句,只有在检索到所有记录后才过滤结果集。这种处理需要进行排序、求和等操作。可以通过 WHERE 子句限制记录的数量来减少这种开销。
14.减少对表的查询操作在包含子查询的SQL语句中,注意减少对表的查询操作。
/*低效SQL*/
SELECT TAB_NAME FROM TABLES
WHERE TAB_NAME =(
SELECT TAB_NAME FROM TAB_COLUMNS
WHERE VERSION = 604)
AND DB_VER =(
SELECT DB_VER FROM TAB_COLUMNS
WHERE VERSION = 604)
/*高效SQL*/
SELECT TAB_NAME FROM TABLES
WHERE (TAB_NAME,DB_VER)=(
SELECT TAB_NAME,DB_VER
FROM TAB_COLUMNS
WHERE VERSION = 604)
15. 使用表别名(Alias) 在一条SQL语句中连接多个表时,请使用表别名,并在每张表前加上别名。这将减少解析时间并减少那些由歧义引起的语法错误。歧义是指由于 SQL 中不同的表同名,当 SQL 语句中出现 this 时,SQL 解析器无法确定 this 的归属。
16. 使用代替IN 在很多基于底层表的查询中,为了满足一个条件,往往需要连接另一个表。在这种情况下,使用(或 NOT )通常会提高查询的效率。
/*低效SQL*/
SELECT * FROM EMP
WHERE EMPNO > 0
AND DEPTNO IN (
SELECT DEPTNO FROM DEPT
WHERE LOC = 'MELB')
/*高效SQL*/
SELECT * FROM EMP
WHERE EMPNO > 0
AND EXISTS (SELECT 1
FROM DEPT
WHERE DEPT.DEPTNO = EMP.DEPTNO
AND LOC = 'MELB')
17. 使用NOT代替NOT IN 在子查询中,NOT IN子句会进行内部排序和合并,对子查询中的表进行全表遍历,效率非常低。为了避免使用 NOT IN,可以将其重写为 Outer Joins 或 NOT。
/*低效SQL*/
SELECT * FROM EMP
WHERE DEPT_NO NOT IN (
SELECT DEPT_NO FROM DEPT
WHERE DEPT_CAT='A')
/*高效SQL*/
SELECT * FROM EMP E
WHERE NOT EXISTS (SELECT 1
FROM DEPT D
WHERE D.DEPT_NO = E.DEPT_NO
AND DEPT_CAT ='A')
18. 用表连接代替表连接通常比表连接更有效。
/*低效SQL*/
SELECT ENAME
FROM EMP E
WHERE EXISTS (SELECT 1
FROM DEPT
WHERE DEPT_NO = E.DEPT_NO
AND DEPT_CAT = 'A')
/*高效SQL*/
SELECT ENAME
FROM DEPT D,EMP E
WHERE E.DEPT_NO = D.DEPT_NO
AND D.DEPT_CAT = 'A'
19. 在提交包含多个表(例如部门表和员工表)信息的查询时,被替换为避免使用 in 子句。一般来说,您可以考虑将其替换为 EXIST。使查询更快,因为 RDBMS 核心模块会在子查询条件满足时立即返回结果。
/*低效SQL*/
SELECT DISTINCT D.DEPT_NO,D.DEPT_NAME
FROM DEPT D,EMP E
WHERE D.DEPT_NO = E.DEPT_NO
/*高效SQL*/
SELECT D.DEPT_NO,D.DEPT_NAME
FROM DEPT D
WHERE EXISTS (SELECT 1
FROM EMP E
WHERE E.DEPT_NO = D.DEPT_NO)
20. 识别低效SQL 下列SQL 工具可以识别低效SQL,前提是需要DBA 权限,否则无法进行查询。
SELECT EXECUTIONS, DISK_READS, BUFFER_GETS,
ROUND ((BUFFER_GETS-DISK_READS)/BUFFER_GETS, 2) Hit_radio,
ROUND (DISK_READS/EXECUTIONS, 2) Reads_per_run,
SQL_TEXT
FROM V$SQLAREA
WHERE EXECUTIONS>0
AND BUFFER_GETS > 0
AND (BUFFER_GETS-DISK_READS)/BUFFER_GETS < 0.8
ORDER BY 4 DESC
此外,您还可以使用 SQL Trace 工具收集执行 SQL 的性能状态数据,包括解析次数、执行次数和 CPU 使用时间。
21. SQL with Plan Plan 是一个很好的分析SQL 语句的工具,它甚至可以在不执行SQL 的情况下分析语句。通过分析,我们可以知道如何连接表oracle数据库优化方法,使用如何扫描表(索引扫描或全表扫描)以及使用的索引名称。
22. SQL PLUS 的 TRACE
SQL> list
SELECT *
FROM dept, emp
WHERE emp.deptno = dept.deptno
SQL> set autotrace traceonly /*traceonly 可以不显示执行结果*/
SQL> /
rows selected.
Execution Plan
----------------------------------------------------------
SELECT STATEMENT Optimizer=CHOOSE
0 NESTED LOOPS
1 TABLE ACCESS (FULL) OF 'EMP'
1 TABLE ACCESS (BY INDEX ROWID) OF 'DEPT'
3 INDEX (UNIQUE SCAN) OF 'PK_DEPT' (UNIQUE)
23. 使用索引提高效率
(1)特点优点:提高效率主键唯一性验证成本:需要空间存储来定期维护和重建索引:
ALTER INDEX
REBUILD (2)索引有两种访问方式
(3)基表选择
(4)多个相等的索引
(5)等式比较优先于范围比较有一个非唯一索引,并且也有一个非唯一索引。
SELECT ENAME FROM EMP
WHERE DEPTNO > 20
AND EMP_CAT = 'A'
这里只使用了索引,那么所有的记录都会与条件一一进行比较。执行路径如下:
TABLE ACCESS BY ROWID ON EMP
INDEX RANGE SCAN ON CAT_IDX
即使对于唯一索引,如果执行范围比较,其优先级也低于非唯一索引的相等比较。
(6)不明确的索引级别当无法判断索引的级别差异时,优化器将只使用一个索引,在WHERE子句中列在最前面。上面有一个非唯一索引,有也是一个非唯一索引。
SELECT ENAME FROM EMP
WHERE DEPTNO > 20
AND EMP_CAT > 'A'
这里只使用索引。执行路径如下:
TABLE ACCESS BY ROWID ON EMP
INDEX RANGE SCAN ON DEPT_IDX
(7)强制索引无效如果两个或多个索引具有相同的排名,您可以强制优化器使用其中一个(通过它检索的记录更少)。
SELECT ENAME
FROM EMP
WHERE EMPNO = 7935
AND DEPTNO + 0 = 10 /*DEPTNO上的索引将失效*/
AND EMP_TYPE || '' = 'A' /*EMP_TYPE上的索引将失效*/
(8)如果索引列是函数的一部分,请避免在索引列上使用计算的 WHERE 子句。优化器将使用全表扫描而不是索引。
/*低效SQL*/
SELECT * FROM DEPT
WHERE SAL * 12 > 25000;
/*高效SQL*/
SELECT * FROM DEPT
WHERE SAL > 25000/12;
(9)如果表中有两个以上(包括两个)索引,则自动选择索引,其中一个是唯一的,另一个是非唯一的。这种情况下,将使用唯一索引而不是完全忽略非唯一索引。
SELECT ENAME FROM EMP
WHERE EMPNO = 2326
AND DEPTNO = 20;
在这里,只有 EMPNO 上的索引是唯一的,所以 EMPNO 索引将用于检索记录。
TABLE ACCESS BY ROWID ON EMP
INDEX UNIQUE SCAN ON EMP_NO_IDX
(10)避免在索引列上使用NOT 一般来说,我们希望避免在索引列上使用NOT,NOT与在索引列上使用函数的效果相同。当遇到NOT时,它会停止使用index 来执行全表扫描。
/*低效SQL: (这里,不使用索引)*/
SELECT * FROM DEPT
WHERE NOT DEPT_CODE = 0
/*高效SQL: (这里,使用索引)*/
SELECT * FROM DEPT
WHERE DEPT_CODE > 0
24. 如果有索引,则将 > 替换为 >=
/*高效SQL*/
SELECT * FROM EMP
WHERE DEPTNO >=4
/*低效SQL*/
SELECT * FROM EMP
WHERE DEPTNO >3
两者的区别在于前者的DBMS会直接跳转到DEPT等于4的第一条记录,而后者会先定位等于3的记录,然后向前扫描到DEPT大于3的第一条记录。
25. 将 OR 替换为 Union(对于索引列) 通常,将 WHERE 子句中的 OR 替换为 UNION 效果会更好。在索引列上使用 OR 将导致全表扫描。请注意,上述规则仅对多个索引列有效。
/*高效SQL*/
SELECT LOC_ID , LOC_DESC , REGION
FROM LOCATION
WHERE LOC_ID = 10
UNIONS
ELECT LOC_ID , LOC_DESC , REGION
FROM LOCATION
WHERE REGION = 'MELBOURNE'
/*低效SQL*/
SELECT LOC_ID,LOC_DESC,REGION
FROM LOCATION
WHERE LOC_ID = 10
OR REGION = 'MELBOURNE'
26. 用 IN 替换 OR
/*低效SQL*/
SELECT * FROM LOCATION
WHERE LOC_ID = 10
OR LOC_ID = 20
OR LOC_ID = 30
/*高效SQL*/
SELECT * FROM LOCATION
WHERE LOC_IN IN (10,20,30)
实际执行效果还有待测试。下面,两者的执行路径似乎是一样的。
27. 避免在索引列上使用is null 和is not null 避免在索引中使用任何可为空的列,索引将不起作用。
/*低效SQL:(索引失效)*/
SELECT * FROM DEPARTMENT
WHERE DEPT_CODE IS NOT NULL;
/*高效SQL:(索引有效)*/
SELECT * FROM DEPARTMENT
WHERE DEPT_CODE >=0;
28. 始终使用索引的第一列 如果索引建立在多列上,优化器将选择仅在 where 子句引用其第一列 ( ) 时使用该索引。
SQL> create index multindex on multiindexusage(inda,indb);
Index created.
SQL> select * from multiindexusage where indb = 1;
Execution Plan
----------------------------------------------------------
SELECT STATEMENT Optimizer=CHOOSE
0 TABLE ACCESS (FULL) OF 'MULTIINDEXUSAGE‘
显然,优化器使用了全表扫描,并在仅引用了索引的第二列时忽略了该索引。
29. 使用UNION ALL代替UNION 当SQL语句需要对两个查询结果集进行UNION时,两个结果集会以UNION-ALL的方式进行合并,然后进行排序后输出最终结果。如果使用 UNION ALL 代替 UNION,则不需要排序,提高效率。由于UNION ALL的结果没有排序oracle数据库优化方法,重复记录也没有过滤,所以是否替换取决于业务需求。
30. UNION的优化 由于UNION对查询结果进行排序,过滤重复记录,所以执行效率不如UNION ALL高。UNION操作会用到一块内存块,所以这块内存的优化也很重要。可以使用以下SQL查询排序的消耗:
select substr(name,1,25) "Sort Area Name",
substr(value,1,15) "Value"
from v$sysstat
where name like 'sort%'
31. 避免改变索引列的类型
比较不同数据类型的数据时,会自动对列进行简单类型转换。
由于内部发生类型转换,将不会使用此索引。几点注意事项:
32. 使用提示 其他提示 这是一项棘手的工作。建议仅针对特定的少量 SQL 优化提示。
33.几个不能使用索引的WHERE子句(1)在下面的例子中,'!='不会使用索引,索引只能告诉你表中存在什么,不能告诉你什么不存在' t 存在于表中。
/*不使用索引*/
SELECT ACCOUNT_NAME
FROM TRANSACTION
WHERE AMOUNT !=0;
/*使用索引*/
SELECT ACCOUNT_NAME
FROM TRANSACTION
WHERE AMOUNT > 0;
(2)在下面的例子中,'||' 是一个字符连接函数。和其他函数一样,索引是禁用的。
/*不使用索引*/
SELECT ACCOUNT_NAME,AMOUNT
FROM TRANSACTION
WHERE ACCOUNT_NAME||ACCOUNT_TYPE='AMEXA';
/*使用索引*/
SELECT ACCOUNT_NAME,AMOUNT
FROM TRANSACTION
WHERE ACCOUNT_NAME = 'AMEX'
AND ACCOUNT_TYPE='A';
(3)在下面的示例中,'+' 是一个数学函数。与其他数学函数一样,索引被禁用。
/*不使用索引*/
SELECT ACCOUNT_NAME,AMOUNT
FROM TRANSACTION
WHERE AMOUNT + 3000 >5000;
/*使用索引*/
SELECT ACCOUNT_NAME,AMOUNT
FROM TRANSACTION
WHERE AMOUNT > 2000 ;
(4)在下面的示例中,相同的索引列无法相互比较,这将启用全表扫描。
/*不使用索引*/
SELECT ACCOUNT_NAME, AMOUNT
FROM TRANSACTION
WHERE ACCOUNT_NAME = NVL(:ACC_NAME, ACCOUNT_NAME)
/*使用索引*/
SELECT ACCOUNT_NAME,AMOUNT
FROM TRANSACTION
WHERE ACCOUNT_NAME LIKE NVL(:ACC_NAME, ’%’)
34. 加入多次扫描 如果将列与一组有限的值进行比较,优化器可能会执行多次扫描并合并结果。例子:
SELECT * FROM LODGING
WHERE MANAGER IN ('BILL GATES','KEN MULLER')
优化器可能会将其转换为以下形式:
SELECT * FROM LODGING
WHERE MANAGER = 'BILL GATES'
OR MANAGER = 'KEN MULLER'
35. 在CBO下使用更具选择性的索引
36. 避免资源密集型操作
37. 优化 GROUP BY,通过在 GROUP BY 之前过滤掉不必要的记录来提高 GROUP BY 语句的效率。
/*低效SQL*/
SELECT JOB,AVG(SAL)FROM EMP
GROUP BY JOB
HAVING JOB = 'PRESIDENT''
OR JOB = 'MANAGER'
/*高效SQL*/
SELECT JOB,AVG(SAL)FROM EMP
WHERE JOB = 'PRESIDENT'
OR JOB = 'MANAGER'
GROUP BY JOB
38. 使用日期 使用日期时要注意,如果日期加上小数点后5位以上,日期会提前到第二天!
SELECT TO_DATE('01-JAN-93'+.99999)
FROM DUAL
结果:
'01-JAN-93 23:59:59'
SELECT TO_DATE('01-JAN-93'+.999999)
FROM DUAL
结果:
'02-JAN-93 00:00:00'
39. 使用 show () 来使用隐式游标,它将执行两个操作。第一次检索记录,第二次检查 TOO MANY ROWS 这个。并且显式游标不执行第二个操作。
40. 分离表和索引 好了,关于SQL优化的内容,这篇文章应该可以满足大部分一般的应用优化需求。刚到这里。
直播