Oracle 数据库查询优化

1. Oracle 数据库查询优化(上百万级记录如何提高查询速度)

  1. 对查询进行优化, 应尽量避免全表扫描, 首先应考虑在 whereorder by 涉及的列上建立索引
  2. 应尽量避免在 where 子句中对字段进行 null 值判断, 否则将导致引擎放弃使用索引而进行全表扫描, 如:
select id from t where num is null

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

select id from t where num=0
  1. 应尽量避免在 where 子句中使用! =或<>操作符, 否则将引擎放弃使用索引而进行全表扫描。
  2. 应尽量避免在 where 子句中使用 or 来连接条件, 否则将导致引擎放弃使用索引而进行全表扫描, 如:
select id from t where num=10 or num=20

可以这样查询:

select id from t where num=10
union all
select id from t where num=20
  1. in 和 not in 也要慎用, 否则会导致全表扫描, 如:
select id from t where num in(1,2,3)

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

select id from t where num between 1 and 3
  1. 下面的查询也将导致全表扫描:
select id from t where name like '%abc%'

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

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

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

select id from t with(index(索引名)) where num=@num
  1. 应尽量避免在 where 子句中对字段进行表达式操作, 这将导致引擎放弃使用索引而进行全表扫描。如:
select id from t where num/2=100

应改为:

select id from t where num=100*2
  1. 应尽量避免在 where 子句中对字段进行函数操作, 这将导致引擎放弃使用索引而进行全表扫描。如:
select id from t where substring(name,1,3)='abc'--name 以 abc 开头的 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' and createdate<'2005-12-1'
  1. 不要在 where 子句中的"="左边进行函数、算术运算或其他表达式运算, 否则系统将可能无法正确使用索引。
  2. 在使用索引字段作为条件时, 如果该索引是复合索引, 那么必须使用到该索引中的第一个字段作为条件时才能保证系统使用该索引, 否则该索引将不会被使用, 并且应尽可能的让字段顺序与索引顺序相一致。
  3. 不要写一些没有意义的查询, 如需要生成一个空表结构:
select col1,col2 into #t from t where 1=0

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

create table #t(...)
  1. 很多时候用 exists 代替 in 是一个好的选择:
select num from a where num in(select num from b)

用下面的语句替换:

select num from a where exists(select 1 from b where num=a.num)
  1. 并不是所有索引对查询都有效, SQL 是根据表中数据来进行查询优化的, 当索引列有大量数据重复时, SQL 查询可能不会去利用索引, 如一表中有字段 sex, male、female 几乎各一半, 那么即使在 sex 上建了索引也对查询效率起不了作用。
  2. 索引并不是越多越好, 索引固然可以提高相应的 select 的效率, 但同时也降低了 insert 及 update 的效率, 因为 insert 或 update 时有可能会重建索引, 所以怎样建索引需要慎重考虑, 视具体情况而定。一个表的索引数最好不要超过 6 个, 若太多则应考虑一些不常使用到的列上建的索引是否有 必要。
  3. 应尽可能的避免更新 clustered 索引数据列, 因为 clustered 索引数据列的顺序就是表记录的物理存储顺序, 一旦该列值改变将导致整个表记录的顺序的调整, 会耗费相当大的资源。若应用系统需要频繁更新 clustered 索引数据列, 那么需要考虑是否应将该索引建为 clustered 索引。
  4. 尽量使用数字型字段, 若只含数值信息的字段尽量不要设计为字符型, 这会降低查询和连接的性能, 并会增加存储开销。这是因为引擎在处理查询和连接时会逐个比较字符串中每一个字符, 而对于数字型而言只需要比较一次就够了。
  5. 尽可能的使用 varchar/nvarchar 代替 char/nchar , 因为首先变长字段存储空间小, 可以节省存储空间, 其次对于查询来说, 在一个相对较小的字段内搜索效率显然要高些。
  6. 任何地方都不要使用 select * from t , 用具体的字段列表代替"*", 不要返回用不到的任何字段。
  7. 尽量使用表变量来代替临时表。如果表变量包含大量数据, 请注意索引非常有限(只有主键索引)。
  8. 避免频繁创建和删除临时表, 以减少系统表资源的消耗。
  9. 临时表并不是不可使用, 适当地使用它们可以使某些例程更有效, 例如, 当需要重复引用大型表或常用表中的某个数据集时。但是, 对于一次性事件, 最好使用导出表。
  10. 在新建临时表时, 如果一次性插入数据量很大, 那么可以使用 select into 代替 create table, 避免造成大量 log , 以提高速度; 如果数据量不大, 为了缓和系统表的资源, 应先 create table, 然后 insert。
  11. 如果使用到了临时表, 在存储过程的最后务必将所有的临时表显式删除, 先 truncate table , 然后 drop table , 这样可以避免系统表的较长时间锁定。
  12. 尽量避免使用游标, 因为游标的效率较差, 如果游标操作的数据超过 1 万行, 那么就应该考虑改写。
  13. 使用基于游标的方法或临时表方法之前, 应先寻找基于集的解决方案来解决问题, 基于集的方法通常更有效。
  14. 与临时表一样, 游标并不是不可使用。对小型数据集使用 FAST_FORWARD 游标通常要优于其他逐行处理方法, 尤其是在必须引用几个表才能获得所需的数据时。在结果集中包括"合计"的例程通常要比使用游标执行的速度快。如果开发时 间允许, 基于游标的方法和基于集的方法都可以尝试一下, 看哪一种方法的效果更好。
  15. 在所有的存储过程和触发器的开始处设置 SET NOCOUNT ON , 在结束时设置 SET NOCOUNT OFF 。无需在执行存储过程和触发器的每个语句后向客户端发送 DONE_IN_PROC 消息。
  16. 尽量避免大事务操作, 提高系统并发能力。
  17. 尽量避免向客户端返回大数据量, 若数据量过大, 应该考虑相应需求是否合理。

2. Oracle SQL 性能优化 40 条 | 收藏了!

  1. SQL 语句执行步骤

语法分析> 语义分析> 视图转换 >表达式转换> 选择优化器 >选择连接方式 >选择连接顺序 >选择数据的搜索路径 >运行"执行计划"

  1. 选用适合的 Oracle 优化器 RULE(基于规则)、 COST(基于成本) 、CHOOSE(选择性)

  2. 访问 Table 的方式全表扫描全表扫描就是顺序地访问表中每条记录, ORACLE 采用一次读入多个数据块 (database block) 的方式优化全表扫描。通过 ROWID 访问表 ROWID 包含了表中记录的物理位置信息, ORACLE 采用索引实现了数据和存放数据的物理位置 (ROWID) 之间的联系, 通常索引提供了快速访问 ROWID 的方法, 因此那些基于索引列的查询就可以得到性能上的提高。

  3. 共享 SQL 语句

  • Oracle 提供对执行过的 SQL 语句进行高速缓冲的机制。被解析过并且确定了执行路径的 SQL 语句存放在 SGA 的共享池中。
  • Oracle 执行一个 SQL 语句之前每次先从 SGA 共享池中查找是否有缓冲的 SQL 语句, 如果有则直接执行该 SQL 语句。
  • 可以通过适当调整 SGA 共享池大小来达到提高 Oracle 执行性能的目的。
  1. 选择最有效率的表名顺序
  • ORACLE 的解析器按照从右到左的顺序处理 FROM 子句中的表名, 因此 FROM 子句中写在最后的表(基础表 driving table) 将被最先处理。
  • 当 ORACLE 处理多个表时, 会运用排序及合并的方式连接它们, 并且是从右往左的顺序处理 FROM 子句。首先, 扫描第一个表 (FROM 子句中最后的那个表)并对记录进行排序, 然后扫描第二个表 (FROM 子句中倒数第二个表), 最后将所有从第二个表中检索出的记录与第一个表中合适记录进行合并。
  • 只在基于规则的优化器中有效。

举例: 表 TAB1 16,384 条记录表 TAB2 1 条记录

/*选择 TAB2 作为基础表 (最好的方法)*/
SELECT COUNT(*) FROM TAB1,TAB2
/*执行时间 0.96 秒*/

/*选择 TAB1 作为基础表 (不佳的方法)*/
SELECT COUNT(*) FROM TAB2,TAB1 
/*执行时间 26.09 秒*/

如果有 3 个以上的表连接查询, 那就需要选择交叉表 (intersection table) 作为基础表, 交叉表是指那个被其他表所引用的表。

/*高效的 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
  1. Where 子句中的连接顺序 Oracle 采用自下而上或自右向左的顺序解析 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'
  1. SELECT 子句中避免使用"*"
  • Oracle 在解析 SQL 语句的时候, 对于"*"将通过查询数据库字典来将其转换成对应的列名。
  • 如果在 Select 子句中需要列出所有的 Column 时, 建议列出所有的 Column 名称, 而不是简单的用"*"来替代, 这样可以减少多于的数据库查询开销。
  1. 减少访问数据库的次数当执行每条 SQL 语句时, ORACLE 在内部执行了许多工作: 解析 SQL 语句 > 估算索引的利用率 > 绑定变量 > 读数据块等等由此可见, 减少访问数据库的次数 , 就能实际上减少 ORACLE 的工作量。

  2. 整个简单无关联的数据库访问如果有几个简单的数据库查询语句, 你可以把它们整合到一个查询中(即使它们之间没有关系), 以减少多于的数据库 IO 开销。虽然采取这种方法, 效率得到提高, 但是程序的可读性大大降低, 所以还是要权衡之间的利弊。

  3. 使用 Truncate 而非 Delete

  • Delete 表中记录的时候, Oracle 会在 Rollback 段中保存删除信息以备恢复。Truncate 删除表中记录的时候不保存删除信息, 不能恢复。因此 Truncate 删除记录比 Delete 快, 而且占用资源少。
  • 删除表中记录的时候, 如果不需要恢复的情况之下应该尽量使用 Truncate 而不是 Delete。
  • Truncate 仅适用于删除全表的记录。
  1. 尽量多使用 COMMIT 只要有可能, 在程序中尽量多使用 COMMIT, 这样程序的性能得到提高, 需求也会因为 COMMIT 所释放的资源而减少。COMMIT 所释放的资源:
  • 回滚段上用于恢复数据的信息。
  • 被程序语句获得的锁
  • redo log buffer 中的空间
  • ORACLE 为管理上述 3 种资源中的内部花费
  1. 计算记录条数
Select count(*) from tablename; 
Select count(1) from tablename; 
Select count(column) from tablename;

一般认为, 在没有主键索引的情况之下, 第二种 COUNT(1) 方式最快。如果只有一列且无索引 COUNT(*) 反而比较快, 如果有索引列, 当然是使用索引列 COUNT(column) 最快。

  1. 用 Where 子句替换 Having 子句避免使用 HAVING 子句, HAVING 只会在检索出所有记录之后才对结果集进行过滤。这个处理需要排序、总计等操作。如果能通过 WHERE 子句限制记录的数目, 就能减少这方面的开销。

  2. 减少对表的查询操作在含有子查询的 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)
  1. 使用表的别名(Alias)当在 SQL 语句中连接多个表时, 请使用表的别名并把别名前缀于每个 Column 上。这样一来, 就可以减少解析的时间并减少那些由 Column 歧义引起的语法错误。Column 歧义指的是由于 SQL 中不同的表具有相同的 Column 名, 当 SQL 语句中出现这个 Column 时, SQL 解析器无法判断这个 Column 的归属。

  2. 用 EXISTS 替代 IN 在许多基于基础表的查询中, 为了满足一个条件 , 往往需要对另一个表进行联接。在这种情况下, 使用 EXISTS(或 NOT EXISTS) 通常将提高查询的效率。

/*低效 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')
  1. 用 NOT EXISTS 替代 NOT IN 在子查询中, NOT IN 子句将执行一个内部的排序和合并, 对子查询中的表执行一个全表遍历, 因此是非常低效的。为了避免使用 NOT IN, 可以把它改写成外连接(Outer Joins)或者 NOT EXISTS。
/*低效 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')
  1. 用表连接替换 EXISTS 通常来说 , 采用表连接的方式比 EXISTS 更有效率 。
/*低效 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'
  1. 用 EXISTS 替换 DISTINCT 当提交一个包含对多表信息(比如部门表和雇员表)的查询时, 避免在 SELECT 子句中使用 DISTINCT。一般可以考虑用 EXIST 替换。EXISTS 使查询更为迅速, 因为 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)
  1. 识别低效的 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 使用时间等 。

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

  2. 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)
  1. 用索引提高效率

(1)特点优点: 提高效率 主键的唯一性验证代价: 需要空间存储 定期维护重构索引:

LTER INDEX <INDEXNAME> REBUILD <TABLESPACENAME>

(2)Oracle 对索引有两种访问模式

  • 索引唯一扫描 (Index Unique Scan)
  • 索引范围扫描 (Index Range Scan)

(3)基础表的选择

  • 基础表 (Driving Table) 是指被最先访问的表(通常以全表扫描的方式被访问)。根据优化器的不同, SQL 语句中基础表的选择是不一样的。
  • 如果你使用的是 CBO (COST BASED OPTIMIZER), 优化器会检查 SQL 语句中的每个表的物理大小, 索引的状态, 然后选用花费最低的执行路径。
  • 如果你用 RBO (RULE BASED OPTIMIZER), 并且所有的连接条件都有索引对应, 在这种情况下, 基础表就是 FROM 子句中列在最后的那个表。

(4)多个平等的索引

  • 当 SQL 语句的执行路径可以使用分布在多个表上的多个索引时, ORACLE 会同时使用多个索引并在运行时对它们的记录进行合并, 检索出仅对全部索引有效的记录。
  • 在 ORACLE 选择执行路径时, 唯一性索引的等级高于非唯一性索引。然而这个规则只有当 WHERE 子句中索引列和常量比较才有效。如果索引列和其他表的索引类相比较。这种子句在优化器中的等级是非常低的。
  • 如果不同表中两个相同等级的索引将被引用, FROM 子句中表的顺序将决定哪个会被率先使用。FROM 子句中最后的表的索引将有最高的优先级。
  • 如果相同表中两个相同等级的索引将被引用, WHERE 子句中最先被引用的索引将有最高的优先级。

(5)等式比较优先于范围比较 DEPTNO 上有一个非唯一性索引, EMP_CAT 也有一个非唯一性索引。

SELECT ENAME FROM EMP
WHERE DEPTNO > 20
AND EMP_CAT = 'A'

这里只有 EMP_CAT 索引被用到, 然后所有的记录将逐条与 DEPTNO 条件进行比较。执行路径如下:

TABLE ACCESS BY ROWID ON EMP
INDEX RANGE SCAN ON CAT_IDX

即使是唯一性索引, 如果做范围比较, 其优先级也低于非唯一性索引的等式比较。

(6)不明确的索引等级当 ORACLE 无法判断索引的等级高低差别, 优化器将只使用一个索引, 它就是在 WHERE 子句中被列在最前面的。DEPTNO 上有一个非唯一性索引, EMP_CAT 也有一个非唯一性索引。

SELECT ENAME FROM EMP
WHERE DEPTNO > 20
AND EMP_CAT > 'A'

这里, ORACLE 只用到了 DEPT_NO 索引。执行路径如下:

TABLE ACCESS BY ROWID ON EMP
INDEX RANGE SCAN ON DEPT_IDX

(7)强制索引失效如果两个或以上索引具有相同的等级, 你可以强制命令 ORACLE 优化器使用其中的一个(通过它, 检索出的记录数量少) 。

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)自动选择索引如果表中有两个以上(包括两个)索引, 其中有一个唯一性索引, 而其他是非唯一性索引。在这种情况下, ORACLE 将使用唯一性索引而完全忽略非唯一性索引。

SELECT ENAME FROM EMP 
WHERE EMPNO = 2326
AND DEPTNO = 20;

这里, 只有 EMPNO 上的索引是唯一性的, 所以 EMPNO 索引将用来检索记录。

SELECT ENAME FROM EMP 
WHERE EMPNO = 2326
AND DEPTNO = 20;

(10)避免在索引列上使用 NOT 通常, 我们要避免在索引列上使用 NOT, NOT 会产生在和在索引列上使用函数相同的影响。当 ORACLE 遇到 NOT, 它就会停止使用索引转而执行全表扫描。

/*低效 SQL: (这里, 不使用索引)*/
SELECT * FROM DEPT
WHERE NOT DEPT_CODE = 0
/*高效 SQL: (这里, 使用索引)*/
SELECT * FROM DEPT
WHERE DEPT_CODE > 0
  1. 用 >= 替代 >如果 DEPTNO 上有一个索引
/*高效 SQL*/
SELECT * FROM EMP
WHERE DEPTNO >=4
/*低效 SQL*/
SELECT * FROM EMP
WHERE DEPTNO >3

两者的区别在于, 前者 DBMS 将直接跳到第一个 DEPT 等于 4 的记录, 而后者将首先定位到 DEPTNO 等于 3 的记录并且向前扫描到第一个 DEPT 大于 3 的记录。

  1. 用 Union 替换 OR(适用于索引列)通常情况下, 用 UNION 替换 WHERE 子句中的 OR 将会起到较好的效果。对索引列使用 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'
  1. 用 IN 替换 OR
/*低效 SQL*/
SELECT * FROM LOCATION
WHERE LOC_ID = 10
OR LOC_ID = 20
OR LOC_ID = 30
/*低效 SQL*/
SELECT * FROM LOCATION
WHERE LOC_ID = 10
OR LOC_ID = 20
OR LOC_ID = 30

实际的执行效果还须检验, 在 ORACLE8i 下, 两者的执行路径似乎是相同的。

  1. 避免在索引列上使用 is null 和 is not null 避免在索引中使用任何可以为空的列, ORACLE 将无法使用该索引。
/*低效 SQL: (索引失效)*/
SELECT * FROM DEPARTMENT
WHERE DEPT_CODE IS NOT NULL;
/*高效 SQL: (索引有效)*/
SELECT * FROM DEPARTMENT
WHERE DEPT_CODE >=0;
  1. 总是使用索引的第一个列如果索引是建立在多个列上, 只有在它的第一个列 (leading column) 被 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‘

很明显, 当仅引用索引的第二个列时, 优化器使用了全表扫描而忽略了索引。

  1. 使用 UNION ALL 替代 UNION 当 SQL 语句需要 UNION 两个查询结果集合时, 这两个结果集合会以 UNION-ALL 的方式被合并, 然后在输出最终结果前进行排序。如果用 UNION ALL 替代 UNION, 这样排序就不是必要了, 效率就会因此得到提高。由于 UNION ALL 的结果没有经过排序, 而且不过滤重复的记录, 因此是否进行替换需要根据业务需求而定。

  2. 对 UNION 的优化由于 UNION 会对查询结果进行排序, 而且过滤重复记录, 因此其执行效率没有 UNION ALL 高。UNION 操作会使用到 SORT_AREA_SIZE 内存块, 因此对这块内存的优化也非常重要。可以使用下面的 SQL 来查询排序的消耗量 :

select substr(name, 1, 25)  "Sort Area Name", 
substr(value, 1, 15)   "Value"
from v$sysstat
where name like 'sort%'
  1. 避免改变索引列的类型

当比较不同数据类型的数据时, ORACLE 自动对列进行简单的类型转换。

/*假设 EMP_TYPE 是一个字符类型的索引列。*/
SELECT *
FROM EMP
WHERE EMP_TYPE = 123
/*这个语句被 ORACLE 转换为: */
SELECT *
FROM EMP
WHERE TO_NUMBER(EMP_TYPE)=123

因为内部发生的类型转换, 这个索引将不会被用到。几点注意:

  • 当比较不同数据类型的数据时, ORACLE 自动对列进行简单的类型转换。
  • 如果在索引列上面进行了隐式类型转换, 在查询的时候将不会用到索引。
  • 注意当字符和数值比较时, ORACLE 会优先转换数值类型到字符类型。
  • 为了避免 ORACLE 对 SQL 进行隐式的类型转换, 最好把类型转换用显式表现出来。
  1. 使用提示(Hints)
  • FULL hint 告诉 ORACLE 使用全表扫描的方式访问指定表。
  • ROWID hint 告诉 ORACLE 使用 TABLE ACCESS BY ROWID 的操作访问表。
  • CACHE hint 来告诉优化器把查询结果数据保留在 SGA 中。
  • INDEX Hint 告诉 ORACLE 使用基于索引的扫描方式。

其他的 Oracle Hints

  • ALL_ROWS
  • FIRST_ROWS
  • RULE
  • USE_NL
  • USE_MERGE
  • USE_HASH 等等。

这是一个很有技巧性的工作。建议只针对特定的, 少数的 SQL 进行 hint 的优化。

  1. 几种不能使用索引的 WHERE 子句(1)下面的例子中, ‘!=’ 将不使用索引 , 索引只能告诉你什么存在于表中, 而不能告诉你什么不存在于表中。
/*不使用索引*/
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,%)
  1. 连接多个扫描如果对一个列和一组有限的值进行比较, 优化器可能执行多次扫描并对结果进行合并连接。举例:
SELECT * FROM LODGING 
WHERE MANAGER IN ('BILL GATES','KEN MULLER')
优化器可能将它转换成以下形式: 
SELECT * FROM LODGING
WHERE MANAGER = 'BILL GATES'
OR MANAGER = 'KEN MULLER'
  1. CBO 下使用更具选择性的索引
  • 基于成本的优化器(CBO, Cost-Based Optimizer)对索引的选择性进行判断来决定索引的使用是否能提高效率。
  • 如果检索数据量超过 30%的表中记录数, 使用索引将没有显著的效率提高。
  • 在特定情况下, 使用索引也许会比全表扫描慢。而通常情况下, 使用索引比全表扫描要块几倍乃至几千倍!
  1. 避免使用耗费资源的操作
  • 带有 DISTINCT, UNION, MINUS, INTERSECT, ORDER BY 的 SQL 语句会启动 SQL 引擎执行耗费资源的排序(SORT)功能。DISTINCT 需要一次排序操作, 而其他的至少需要执行两次排序。
  • 通常, 带有 UNION, MINUS, INTERSECT 的 SQL 语句都可以用其他方式重写。
  1. 优化 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
  1. 使用日期当使用日期时, 需要注意如果有超过 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'
  1. 使用显示游标 (CURSORS) 使用隐式的游标, 将会执行两次操作。第一次检索记录, 第二次检查 TOO MANY ROWS 这个 exception。而显式游标不执行第二次操作。

  2. 分离表和索引

  • 总是将你的表和索引建立在不同的表空间内(TABLESPACES)。
  • 决不要将不属于 ORACLE 内部系统的对象存放到 SYSTEM 表空间里。
  • 确保数据表空间和索引表空间置于不同的硬盘上。

好了, 关于 Oracle SQL 优化的内容, 这一篇应该满足常规大部分的应用优化要求。就先到这里了。

  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

云满笔记

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值