ORACLE9I(转载)

 

 

第一章 ORACLE9I SQL新增内容

1.1 ORACLE9i新增数据类型及函数的使用

 

 ORACLE9I以前数据类型

 l        varchar2
l        number
l        date

 varchar2(n) <=4000

number(precision,scale) default 38 p[1,38];s[-84,127]

date dd-mon-yy

日期型数据使用需要注意的地方

 1.日期格式的具体显示形式由数据库初始化参数NLS_DATE_LANGUAGE, NLS_DATE_FORMAT来决定。

举例:
alter session set nls_date_language=american;

2.注意ORACLE的日期型数据是带时间的

举例:(略)

3.字符数据转换到数字或日期类型数据时,FMT串中有'fx'前导标识,指示提供的值与格式严格匹配:
select to_date('2000-01-02','fxyyyy-mm-dd') from dual;

4.RR年格式,世纪是基于默认规则变化的
     世纪 指定:00-49    50-99
    当前 00-49       当前     当前-1
    当前 50-99       当前+1   当前
(助记1:99年时出现01年为:1999年时记录2001年数据
  01年时出现99年为:2001年时记录1999年数据“数据不可能跨度超过半个世纪以上” )

 (助记2: 将一个世纪分为两段:00-49,50-99。
       [当前段小,] 指定段大è世纪减1
       [当前段大,] 指定段小è世纪加1
       当前段为小,所以我们只需注意指定段大的情况.如现有指定50指的是1950年.

alter session set nls_date_language=american;

alter session set nls_date_format='rrmmdd';

select to_char(to_date('500101'),'yyyymmdd') from dual;)

例:
select to_char(to_date('25-jan-99','dd-mon-rr'),'yyyy-mm-dd') from dual;
TO_CHAR(TO
----------
1999-01-25

ORACLE9I对日期型数据的扩充
1.interval year to month,interval day to second时间间隔类型

数据定义形式:
 INTERVAL YEAR [(year_precision)] TO MONTH
 INTERVAL DAY [(day_precision)] TO SECOND [(fractional_seconds_precision)]

字面引用形式:
INTERVAL '123-2' YEAR(3) TO MONTH
 An interval of 123 years, 2 months. You must specify the leading field precision if it is greater than the default of 2 digits. 

INTERVAL '123' YEAR(3)
 An interval of 123 years 0 months.

INTERVAL '300' MONTH(3)
 An interval of 300 months.
INTERVAL '4' YEAR
 Maps to INTERVAL '4-0' YEAR TO MONTH and indicates 4 years. 

INTERVAL '50' MONTH

 Maps to INTERVAL '4-2' YEAR TO MONTH and indicates 50 months or 4 years 2 months.

 INTERVAL '123' YEAR
Returns an error, because the default precision is 2, and '123' has 3 digits.

 INTERVAL '4 5:12:10.222' DAY TO SECOND(3)
4 days, 5 hours, 12 minutes, 10 seconds, and 222 thousandths of a second.

 INTERVAL '4 5:12' DAY TO MINUTE 4 days, 5 hours and 12 minutes.

 INTERVAL '400 5' DAY(3) TO HOUR
400 days 5 hours.

INTERVAL '400' DAY(3)
400 days.

INTERVAL '11:12:10.2222222' HOUR TO SECOND(7)
11 hours, 12 minutes, and 10.2222222 seconds.

INTERVAL '11:20' HOUR TO MINUTE
11 hours and 20 minutes.

 INTERVAL '10' HOUR
10 hours.

 INTERVAL '10:22' MINUTE TO SECOND
10 minutes 22 seconds.

 INTERVAL '10' MINUTE
10 minutes.

 
INTERVAL '4' DAY
 4 days.

INTERVAL '25' HOUR
 25 hours.

INTERVAL '40' MINUTE
 40 minutes.

INTERVAL '120' HOUR(3)
 120 hours.

INTERVAL '30.12345' SECOND(2,4)
 30.1235 seconds. The fractional second '12345' is rounded to '1235' because the precision is 4.

2.TIMESTAMP [(fractional_seconds_precision)]
数据定义形式:

TIMESTAMP [(fractional_seconds_precision)] WITH TIME ZONE

TIMESTAMP [(fractional_seconds_precision)] WITH LOCAL TIME ZONE

 字面引用形式:TIMESTAMP'1997-01-31 09:26:50.124'

 

类似有: DATE’1997-01-31

相对应的毫秒的FORMAT指示为:”xff(n)”

 举例:
新建表newtime

列名称       类型
ID               NUMBER
D1                     TIMESTAMP(6)
INTER1      INTERVAL YEAR(2) TO MONTH
T_W_TZ     TIMESTAMP(6) WITH TIME ZONE
T_W_LTZ   TIMESTAMP(6) WITH LOCAL TIME ZONE

 TIMESTAMP WITH TIME ZONE与TIMESTAMP WITH LOCAL TIME ZONE比较
TIMESTAMP WITH LOCAL TIME ZONE总是显示本地TIME ZONE的时间。

 insert into newtime (id,t_w_tz,t_w_ltz)
values(2,timestamp'2006-01-12 09:00:00 +9:00',timestamp'2006-01-12 09:00:00 +9:00')

 select * from newtime;
12-1月 -06 09.00.00.000000 上午 +09:00
12-1月 -06 08.00.00.000000 上午

 新的时间函数CURRENT_DATE与原时间函数SYSDATE比较
SQL> alter session set TIME_ZONE = '+9:0'
2 /

会话已更改。

 SQL> select to_char(current_date,'yyyymmdd hh24:mi:ss') from dual;
TO_CHAR(CURRENT_D
-----------------
20060116 18:48:56

SQL> select to_char(sysdate,'yyyymmdd hh24:mi:ss') from dual;

TO_CHAR(SYSDATE,'
-----------------
20060116 17:49:27 

ORACLE9I SQL需注意的地方

 1. 比较运算真值表

FALSE AND NULL == FALSE (与为:一项为FALSE,结果为FALSE)

TRUE OR NULL == TRUE (或为:一项为TRUE,结果为TRUE)

2. ANY 和 ALL操作符

<ANY 小于最大

>ANY 大于最小

=ANY 等价于IN

括号打开后是OR关系,枚举列表中有NULL不影响比较结果。

 <ALL 与<ANY类似,不同的是枚举列表中不能有空

>ALL 与>ANY类似,不同的是枚举列表中不能有空

<>ALL 等价于NOT IN

括号打开后是AND关系,枚举列表中有NULL,比较结果恒为NULL。

例:

Create table t1( c1 number primary key,c2 number,c3 varchar2(20));

SQL> insert into t1 values (1,1,1);

已创建 1 行。

SQL> insert into t1 values (2,null,null);

已创建 1 行。

 

SQL> select * from dual where 1 not in(select c2 from t1);

未选定行

--是期望的结果.因为1在IN结果集中.条件表达式的值为FALSE.结果集为空.

 

SQL> select * from dual where 3 not in(select c2 from t1);

未选定行

--不是期望的结果.因为3确实不在IN结果集中.

 SQL> select * from dual where 3 not in(select c1 from t1);

DUMMY
----------
X

 =ALL 一般总选不出数据

!=ANY 一般总能选出数据(除非象1!=ANY(1))

[IN] == [=ANY] , [NOT IN] == [!=ALL]
IN子句打开以后是OR关系。所以枚举列表中有一项满足条件表达式即为TRUE。

 NOT IN子句打开后是AND关系。当枚举列表中有NULL值时,其比较表达式的值为空。因为 A!=NULL为NULL,A AND NULL=NULL。

所以,NOT IN子句中不应该有NULL值的存在,否则会有问题。
(这不是语言缺陷,而是我们直觉造成的不能理解。)

 3. LIKE子句对特殊字符的处理
LIKE ‘AC\_%’ ESCAPE ‘\’

 4. 排序列值为NULL时的处理
默认NULL在升序时排列在结果集尾部;降序时排列在结果集首部。
可以加NULLS子句对默认行为加以修改。
ASC NULLS FIRST/DESC NULLS LAST

 5.不可以在GROUP BY子句中使用列别名
 不可以在GROUP BY子句中使用列别名。对于显示的列是复杂的表达式的情况也不例外。即也要写出完整的表达式。

 6.ORDER BY子句列名部分可以有多种选择
   ORDER BY 子句中的列名可以是:列名/表达式/列别名/序号(从1开始)

7.分组函数均不包含NULL值行,只有COUNT(*)包含NULL值行
注意当列值中有NULL值时是不包含进函数计算范围的。当然,COUNT(*)是不会包含所有列都是NULL值的情况的。

 8.子查询限制
子查询限定在比较操作符的右边
ORDER BY子句在子查询中不需要(ORACLE8i以前视图中不能有ORDER BY子句),但在TOP-N分析语句中可以使用。

 

 

Oracle9i新增函数

单行函数

l         单行函数不能出现在HAVING子句中
l         NULL为参数导致结果为NULL,用NVL,NVL2解决

 1.       字符函数

Concat(str1,str2)          str1+str2

Initcap(str)                   ‘abc’à ‘Abc’

Instr(c1,c2[,i[,j]])         c1中找到第j个c2, 从第i个字符开始扫描.0没找到.

Lpad(c1,i[,c2])                     用c2前补c1, 总长度限定为i. c2默认为空格.

trim([[c1] c2 from ] c3) c1:LEADING|TRAILING|BOTH;c2为要去掉的字符.

Replace(c1,c2[,c3])              将c1中的c2替换成c3

 2.       数字函数

Ceil(n)                  上取整

Mod(n1,n2)            求余

Round(n1,n2)        舍入

Trunc(n1,n2)         截取

 3.       日期函数

Sysdate                               return date

   Current_date                       return timestamp with session time zone

   Current_timestamp[(p)]      return timestamp with time zone

   Systimestamp[(p)]                return timestamp with time zone

   Localtimestamp[(p)]             return timestamp

   Extract(c from dt)               

              c: year,month,day,hour,min,second,timezone_hour|minute|region|abbr

           dt:timestamp,interval

   New_time(dt,tz1,tz2)            return dt with tz2

   Round(d[,fmt])

   Trunc(d[,fmt])

 4.       转换函数

Cast(c as t)                          c转换为类型t

      CAST(SYSDATE AS VARCHAR2(24))

Numtodsinterval(x,c)           将day间隔改为Day to second间隔.

        Numtodsinterval(33,’hour’)

Numtoyminterval(x,c)         将day间隔改为year to month间隔.

        Numtoyminterval(1,’month’)

 5.       判空函数

Coalesce(<exp_list>)           返回第一个非空表达式

Nvl(x1,x2),nvl2(x1,x2,x3)

 Nvl:x1=null=>x2

 Nvl2:x1=null?x3,x2

 6.       其它函数

Decode(exp,m1,r1…d)

Dump(x,[,fmt[,n1[,n2]]])

        Fmt:进制说明’1008’八进制,’1010’十进制,’1016’十六进制,’1017’单字符,

              N1:x的n1字节开始, N2:长度

            返回:字符串描述. X的类型(2 number,12 date, 96 varchar2..); X字节长度..

       Nullif(x1,x2)                      

if x1=x2 then null else x1(x1!=null)

       vsize(x)                              返回X的字节长度

 多行函数 (组函数)

l         分组函数出现在SELECT子句或HAVING子句中,不能出现在WHERE子句中

l         列别名不能出现在GROUP BY子句中

l         GROUP BY子句中的列默认升序排列

l         GROUP BY子句列列表大于等于SELECT子句中非分组函数列列表

l         ORDER BY子句在GROUP BY子句之后

 

Avg([distinct|all] n )/sum([distinct|all] n)

 例:用EMP.COMM举例

SQL> select deptno,comm from emp where comm is not null;

  DEPTNO       COMM

---------- ----------

        20        300

        30        300

        30        500

        30       1400

        30          0

 

SQL> select sum(comm),sum(distinct comm) from emp where comm is not null;

  SUM(COMM) SUM(DISTINCTCOMM)

---------- -----------------

      2500              2200

 

select 2500/5,2200/4,avg(comm),avg(distinct comm) from emp

   2500/5     2200/4 AVG(COMM) AVG(DISTINCTCOMM)

---------- ---------- ---------- -----------------

       500        550        500               550

 Count({*|[distinct|all]<x>})

 只有*(或常量如数字1)才能将NULL值行记数

 

1.2 ORACLE9i新增SQL语法

CASE语句可以在SQL中

 
语法图:
CASE { simple_case_expression | searched_case_expression } [else_clause] END
(User:oe)
  SELECT cust_last_name,
  CASE credit_limit WHEN 100 THEN 'Low'
  WHEN 5000 THEN 'High'
   ELSE 'Medium' END
   FROM customers;
 
  CUST_LAST_NAME       CASECR
 -------------------- ------
 ...
  Bogart               Medium
  Nolte                Medium
  Loren                Medium
  Gueney               Medium

 

SELECT AVG(CASE WHEN e.salary > 2000 THEN e.salary
   ELSE 2000 END) "Average Salary" from employees e;
 
Average Salary
--------------
    6461.68224
 

增强的DATETIME表达式

 

datetime_value_expr AT
{ LOCAL
| TIME ZONE { ' [ + | - ] hh:mm'
              | DBTIMEZONE
              | SESSIONTIMEZONE
              | 'time_zone_name'
              | expr
              }
}

SQL> select timestamp'2000-01-02 8:00:00.234' from dual;

 

TIMESTAMP'2000-01-028:00:00.234'

---------------------------------------------------------------------------

02-1月 -00 08.00.00.234000000 上午

 

SQL> select timestamp'2000-01-02 8:00:00.234' at time zone '9:00' from dual;

 

TIMESTAMP'2000-01-028:00:00.234'ATTIMEZONE'9:00'

---------------------------------------------------------------------------

02-1月 -00 09.00.00.234000000 上午 +09:00

 

 

增强的分组查询

 

create table test(

year number(4),

region char(7),

dept char(2),

profit number);

insert into test values(1995,'West','A1',100);

insert into test values(1995,'West','A2',100);

insert into test values(1996,'West','A1',100);

insert into test values(1996,'West','A2',100);

insert into test values(1995,'Central','A1',100);

insert into test values(1995,'East','A1',100);

insert into test values(1995,'East','A2',100);

 

----------------------------------------------------------------------
Example 1: ROLLUP
----------------------------------------------------------------------
 
SQL> select year, region, sum(profit), count(*)
 2 from test
 3 group by rollup(year, region);
 
      YEAR     
REGION
SUM(PROFIT)
COUNT(*)
1995
Central
100
1
1995
East
200
2
1995
West
200
2
1995                 
         
500
5
1996
West            
200          
2
1996                 
 
200          
2
 
 
700
7
 
7 rows selected.
 
A summary of the above information in tabular form:
 
Year Central(A1+A2) East(A1+A2) West(A1+A2)     
1995 (100+NULL)      (100+100)    (100+100)    500
1996 (NULL+NULL)     (NULL+NULL) (100+100)    200
                                              700
 
等价的一般查询:
select year, region, sum(profit), count(*)
from test
group by year, region
union all
select year, null, sum(profit), count(*)
from test
group by year
union all
select null,null,sum(profit),count(*) 
from test
 
注:
产生n+1种分组(0,0),(0,1),(1,1).如下所示.
(n=3时为000,001,011,111四种分组)
----------------------------------------------------------------------
Example 2: ROLLUP and GROUPING()
----------------------------------------------------------------------
 
SQL> select year, region, sum(profit),
 2         grouping(year) "Y", grouping(region) "R"
 3 from test
 4 group by rollup (year, region);
 
      YEAR REGION SUM(PROFIT)          Y          R
---------- ------- ----------- ---------- ----------
      1995 Central         100          0          0
      1995 East            200          0          0
      1995 West            200          0          0
      1995                 500          0          1
      1996 West            200          0          0
      1996                 200          0          1
                           700          1          1
 
7 rows selected.
 
Note, GROUPING(expr) function returns 1 for super-aggregate rows if the null 
value for expr represents the set of all values. 
 
----------------------------------------------------------------------
Example 3: CUBE
----------------------------------------------------------------------
 
SQL> select year, region, sum(profit), count(*)
 2 from test
 3 group by cube(year, region);
 
     YEAR REGION SUM(PROFIT)   COUNT(*)
---------- ------- ----------- ----------
      1995 Central         100          1
      1995 East            200          2
      1995 West            200          2
      1995                 500          5
      1996 West            200          2
      1996                 200          2
           Central         100          1
           East            200          2
           West            400          4
                           700          7
 
Thus, in addition to the subtotals produced by ROLLUP, the CUBE operator 
completes all possible entries in a crosstab report.
 
A summary of the above information in tabular form:
 
Year Central(A1+A2) East(A1+A2) West(A1+A2)     
1995 (100+NULL)      (100+100)    (100+100)    500
1996 (NULL+NULL)     (NULL+NULL) (100+100)    200
      100             200          400         700
 
等价的一般查询:
select year, region, sum(profit), count(*)
from test
group by year, region
union all
select year, null, sum(profit), count(*)
from test
group by year
union all
select null, region, sum(profit), count(*)
from test
group by region
union all
select null,null,sum(profit),count(*) 
from test
 
注:
产生2^n种分组(0,0),(0,1),(1,0),(1,1).如下所示.
(n=3时为:000,001,010,100,011,110,101,111)
----------------------------------------------------------------------
Example 4: CUBE and GROUPING()
----------------------------------------------------------------------
 
SQL> select year, region, sum(profit),
 2         grouping(year) "Y", grouping(region) "R"
 3 from test
 4 group by cube (year, region);
 
      YEAR REGION SUM(PROFIT)          Y          R
---------- ------- ----------- ---------- ----------
      1995 Central         100          0          0
      1995 East            200          0          0
      1995 West            200          0          0
      1995                 500          0          1
      1996 West            200          0          0
      1996                 200          0          1
           Central         100          1          0
           East            200          1          0
           West            400          1          0
                           700          1          1
 
10 rows selected.
 
 
Note, GROUPING(expr) function returns 1 for super-aggregate rows if the null 
value for expr represents the set of all values. 
 

 

WITH子句

WITH子句是ORACLE9i新增的SQL语法。它定义重复使用的SELECT命令块。使在单条SQL语句中可多次重复利用同一结果集。

 

例:

WITH T01 AS (SELECT * FROM DEPT),

 T02 AS (SELECT * FROM DEPT)

SELECT T01.DEPTNO,T02.DEPTNO FROM T01,T02

WHERE T01.DEPTNO=T02.DEPTNO;

 

 

1.3 ORACLE9i层次查询

 在一个数据库表中,当主键列与外键列同在这一张表时,数据的分布就是一个树形结构。最一般的例子,如BBS论坛中的贴子。一个主贴后跟多个子贴,其中的某些子贴比较引人注意的话,她又会充当这层的主贴后跟无数的子贴。以此类推,无穷往复。类似BBS论坛的数据一般就是用树形结构存储的。ORACLE9i层次查询就是用来解决树形结构数据遍历问题的。

 

ORACLE9i层次查询有以下几个关键语法结构

l        CONNECT BY PRIOR子句

l        START WITH 子句

l        LEVEL伪列

 

CONNECT BY PRIOR这个的关键子句来指定树形结构遍历的方向。(PRIOR为“居先”之意,即表明遍历的方向。)

CONNECT BY PRIOR 主键列=外键列 从上向下遍历

CONNECT BY PRIOR 外键列=主键列 从下向上遍历

注意:

主键列为树叶,外键列为树根. 作为上下级关系的例子,主键列看做下级,外键列看做上级.

 

START WITH 指定遍历初始节点。若不指定则以所有行为初始节点均遍历一遍,所以不指定初始节点没有太大的实际意义。

 

LEVEL伪列显示节点所在树形结构中的层数。靠近树根,层数渐低。树根为1。

 

WHERE子句只控制节点数。

 

例:(从上向下遍历)

SELECT lpad(empno,level*4,' ') FROM EMP CONNECT BY PRIOR EMPNO=MGR --START WITH EMPNO=7839

 

(从下向上遍历)

SELECT lpad(empno,level*4,' ') FROM EMP CONNECT BY PRIOR MGR=empno --START WITH EMPNO=7369

 

 

1.4 ORACLE9i高级DML与DDL

   ORACLE高级DML与DDL(9I)

1.4.1 DML

1.WITH CHECK OPTION选项的用法

在线视图中的WITH CHECK OPTION选项限制的是数据修改范围。

如:

   下面的UPDATE语句将不会执行通过,因为它修改后的结果在视图范围之外。

update (select * from dept where deptno>30 with check option)

      set deptno=29 where deptno=40 (应将29改为大于30)

   

       下面的INSERT语句将不会执行通过,因为新插入的值在视图范围之外。

       INSERT INTO (SELECT * FROM DEPT WHERE DEPTNO>50 WITH CHECK OPTION)

       VALUES (44,'ABCD','EFGH')

 

2.INSERT/UPDATE语句中可以使用DEFAULT关键字

       INSERT/UPDATE语句中使用常量的位置均可以使用DEFAULT关键字。若对象定义中没有DEFAULT值的定义,则用NULL值代替。

 

3.TOP-N分析语句

 

SELECT <COL_LIST>,ROWNUM

FROM (SELECT <COL_LIST>

              FROM <TABLE>

              [WHERE ..]

              [ORDER BY TOP-N-COLS])

WHERE ROWNUM<=N

 

例:查询工资较高的前5位员工

SELECT * FROM (SELECT ENAME,SAL FROM EMP ORDER BY 2 DESC)

WHERE ROWNUM<6

 

4.MERGE 语句

MERGE INTO [schema .] table [t_alias] USING [schema .] 
{ table | view | subquery } [t_alias] ON ( condition ) 
WHEN MATCHED THEN merge_update_clause 
WHEN NOT MATCHED THEN merge_insert_clause;

 

merge_update_clause:

UPDATE SET column = { expr | DEFAULT } [, column = { expr | DEFAULT }]...

 

merge_insert_clause:

INSERT ( column [, column]... ) VALUES ( expr [, expr]... )

 

例:

MERGE INTO DEPT1 D

USING DEPT

ON (D.DEPTNO = DEPT.DEPTNO)

WHEN MATCHED THEN UPDATE SET D.DNAME = D.DNAME||'1'

WHEN NOT MATCHED THEN INSERT(D.DEPTNO,D.DNAME,D.LOC) VALUES(DEPT.DEPTNO,DEPT.DNAME,DEPT.LOC)

 

注意:不能在UPDATE在ON子句中的列。否则提示ON子句中的列为“无效的标识符”(有点摸不着头脑的提示!)

MERGE INTO DEPT1 D

USING DEPT

ON (D.DEPTNO = DEPT.DEPTNO)

WHEN MATCHED THEN UPDATE SET D.DEPTNO = D.DEPTNO + 1

WHEN NOT MATCHED THEN INSERT(D.DEPTNO,D.DNAME,D.LOC) VALUES(DEPT.DEPTNO,DEPT.DNAME,DEPT.LOC)

 

ERROR 位于第 3 行:

ORA-00904: "D"."DEPTNO": 无效的标识符

 

5. 多表INSERT语句

CREATE TABLE D1 AS SELECT * FROM DEPT WHERE ROWNUM<1;

CREATE TABLE D2 AS SELECT * FROM DEPT WHERE ROWNUM<1;

CREATE TABLE D3 AS SELECT * FROM DEPT WHERE ROWNUM<1;

 

无条件多表插入:

INSERT ALL

 INTO D1 --(DEPTNO,DNAME,LOC) VALUES(DEPTNO,DNAME,LOC)

 INTO D2

 INTO D3

SELECT * FROM DEPT WHERE DEPTNO>=30

 

有条件多表插入:

INSERT ALL

WHEN DEPTNO<20 THEN INTO D1

WHEN DEPTNO>=20 AND DEPT<30 THEN INTO D2

WHEN DEPTNO>=30 THEN INTO D3

SELECT * FROM DEPT;

 

INSERT ALL|FIRST

WHEN DEPTNO<20 THEN INTO D1

WHEN DEPTNO<20 THEN INTO D2

WHEN DEPTNO>=30 THEN INTO D3

SELECT * FROM DEPT;

 

PIVOTING(绕线轴)多表插入:

将关系中的多个属性合并成一个属性。

CREATE TABLE SALES(E_ID NUMBER PRIMARY KEY,S_MON NUMBER,S_TUE NUMBER);

INSERT INTO SALES VALUES (1,100,200);

INSERT INTO SALES VALUES (2,100,200);

INSERT INTO SALES VALUES (3,100,200);

INSERT INTO SALES VALUES (4,100,200);

CREATE TABLE SALES_INFO (E_ID NUMBER,SALES NUMBER);

 

INSERT ALL

 INTO SALES_INFO VALUES(E_ID,S_MON)

 INTO SALES_INFO VALUES(E_ID,S_TUE)

SELECT E_ID,S_MON,S_TUE FROM SALES;

 

注意:

虽然SQL很长,但整条语句只能有一个分号(“;”)。开始写这样的语句时经常犯这样的错误。

 

1.4.2 DDL

 

 

第二章 SQL调优

2.1 SQL调优的基本目标

对于在线事务处理系统(OLPT)的用户而言,SQL的响应时间是衡量SQL是否最优的一个重要指标。

 

抛开硬件因素(单位时间数据流量),取得的信息量越少,则所需时间越少。SQL的响应越快。基于这样的分析,SQL的调整往往归结为对SQL存取代价的调整甚至对硬件I/O的调整。

 

对SQL存取代价的调整现在已经由新一代的CBO优化器完成。硬件的调整由硬件厂商及底层DBA来完成。用户所要做的可能只是提供在什么表的什么列上建立什么样的索引。那么还有对现有索引的维护以及CBO优化器要求的系统存储统计信息的收集均可由DBA来完成。

 

随着产品数据库系统结构的变化,以及数据量的增长,可能产生系统性能下降的情况。系统DBA应能够根据对系统的健康情况报告, 分析系统的问题所在,并加以调整。如果涉及到对系统产生负面影响的SQL,系统DBA会将优化后的SQL提供给用户,由用户修改相应的程序段(这个过程甚至有时也是不必要的)。

 

对于ORACLE9i系统,用户涉及的SQL优化部分应该有以下几方面内容。

1、         良好的数据库结构设计(大部分产品数据库结构设计应该由有经验的DBA来完成)。

2、         建立必要的索引。

3、         有规律地收集数据库对象统计信息(也可由专门的DBA来完成)。

4、         找出产品数据库中消耗较大的SQL语句。并加以调整。(一般由数据库技术专家来完成)

 

新版优化器的目的在于节省SQL优化时间,用户可以把大部分时间用在系统结构设计、数据规划以及SQL的实现上。

 

2.2 用户跟踪优化SQL方法

在SQLPLUS下SQL语句跟踪分析有以下三种方法。用户可以在开发过程中预先对SQL进行初步的分析。例如,是否运用到了索引,预期的响应时间是否达到等。

   

1.执行SQLPLUS命令跟踪分析SQL

1)建立PLAN_TABLE表。

   PLAN_TABLE表存放SQL执行计划的信息。

在SQLPLUS下连接预跟踪用户,同时执行@?/rdbms/admin/utlxplan.sql建立PLAN_TABLE表。

 2)建立并授予PLUSTRACE角色。

   建立并授予PLUSTRACE角色将提供执行跟踪功能所必需的权限。

   在SQLPLUS下连接预跟踪用户,同时执行@?/sqlplus/admin/plustrce.sql建立并授予PLUSTRACE角色。

 3) SQLPLUS下设置跟踪环境变量。

 SQLPLUS下执行环境设置命令 :

 SQLPLUS>SET AUTOTRACE [OFF|ON|TRACEONLY] [EXPLAIN|STATISTICS]

 说明:

 ON:                产生结果集、执行计划、统计

 TRACEONLY:产生执行计划、统计。(SQL执行但不显示结果集   )

 ON EXPLAIN:产生结果集、执行计划。没有统计

 TRACEONLY STATISTICS:仅产生统计

 注:不管他是否要显示结果集均要执行SQL。

2.执行系统包跟踪分析SQL

1)建立PLAN_TABLE表。

   PLAN_TABLE表存放SQL执行计划的信息。

在SQLPLUS下连接预跟踪用户,同时执行@?/rdbms/admin/utlxplan.sql建立PLAN_TABLE表。

 2)为预分析的SQL产生执行计划。

   SQLPLUS下执行:

   SQLPLUS>explain plan [set statement_id=’<id_name>’] [into <my_plan_table>]

             For <select 语句>;

 3) 查询SQL分析结果。

   SQLPLUS下执行@?/sqlplus/admin/utlxpls.sql(不包含并行SQL)或执行@?/sqlplus/admin/utlxplp.sql(包含并行SQL)

 注:此种跟踪分析SQL的方法仅用于得到SQL的执行计划。它有时对用户来说有个附带的好处就是,她不实际地执行SQL。这对于分析复杂的实际执行时间可能很长的SQL是有好处的。而第一种方法肯定是要执行SQL的,不管他是否要显示结果集均是如此。

3.设置系统参数跟踪分析特定SESSION的SQL

这种跟踪分析SQL的方法适用于在程序测试阶段,确定某特定程序模块内发出的SQL的执行情况。通过设置系统初始化参数,利用系统自带的跟踪机制跟踪SQL。用户再利用格式化工具将跟踪结果输出,最终得到用户可以看得懂的跟踪结果。

 1)确定跟踪级别
可以对实例级、会话级实施SQL跟踪。

实例级跟踪
此级别的跟踪因为对系统消耗较大,所以不太适用。
它需要修改初始化参数SQL_TRACE=TRUE

 会话级跟踪

有三种设置会话级跟踪的方法:

1.当前会话下执行ALTER SESSION命令

Sqlplus>alter session set sql_trace=true;

与前两种跟踪实现的功能类似。

 2. 在当前会话下执行系统包
Sqlplus>EXEC DBMS_SESSION.SET_SQL_TRACE(TRUE|FALSE)
等价于第一种设置方法。

 3.在任意有执行系统包DMBS_SYSTEM权限的会话下执行方法DBMS_SYSTEM.SET_SQL_TRACE_IN_SESSION(<SID>,<SERIAL#>,TRUE|FALSE)

方法中的<SID>,<SERIAL#>由当前运行系统的动态视图来得到。例如:当前运行系统的动态视图v$session的记录如下所示:

select sid,serial#,username,machine from v$session;

       SID    SERIAL# USERNAME        MACHINE

---------- ---------- --------------- --------------------

         1          1                 ZHANGHONGJING

         2          1                 ZHANGHONGJING

         3          1                 ZHANGHONGJING

         4          1                 ZHANGHONGJING

         5          1                 ZHANGHONGJING

         6          1                 ZHANGHONGJING

         7          1                 ZHANGHONGJING

         8          1                 ZHANGHONGJING

         9         54 SYS             SOFTBRAIN\ZHANGHONGJ

                                      ING

 

注:查看当前SESSION的SID的SQL命令为:

    Select sid from v$mystat where rownum<2;

要跟踪当前SESSION内发出所有SQL,我们可以执行:

SQLPLUS>EXEC DBMS_SYSTEM.SET_SQL_TRACE_IN_SESSION(9,54,TRUE);

 3.格式化跟踪结果

当执行完成上面两步后,相应的SESSION发出任何SQL,系统将做出跟踪动作。跟踪得到的结果存放在系统初始化参数指定的地方。这个系统初始化参数为USER_DUMP_DEST指定的操作系统目录。在实施跟踪前最好将此目录下内容备份并清除。操作系统下执行命令TKPROF <TRACEFILE.TRC> <OUTPUT.TXT> RECORD=<跟踪到的sql命令行> 格式化跟踪结果。

 

2.3系统性能报告中针对高消耗SQL的分析

(略述如下:)

系统的响应时间一般是与CPU相关的或者是与I/O相关的。

Response Time = Service Time + Wait Time(no Idle wait time)
Service Time = CPU Parse Time + CPU Recursive + CPU Other

 

Report报告中对应项:

Service Time : CPU used by this session

CPU Parse : parse time CPU

CPU Recursive : recursive CPU usage

CPU Other = Service Time - CPU Parse - CPU Recursive

 

1. CPU Other/Response Time比率大时,找存取BLOCK最多的SQL

SQL order by gets中buffer Gets,Logical I/Os

 

2. CPU Parse/Response Time比率大时,找分析最多的SQL

SQL order by Parse calls

 

3. 等待事件:db file sequential read(for single-block reads),db file scattered read(for multi-block reads)比率大时,找磁盘读最多的SQL

SQL order by reads

 

各部分响应时间计算 (pre-Oracle9i Release 2)

这个例子是CPU Other占比率大:

 
Top 5 Wait Events                                                             
~~~~~~~~~~~~~~~~~                                             Wait     % Total
Event                                               Waits Time (cs)   Wt Time
-------------------------------------------- ------------ ------------ -------
direct path read                                    4,232       10,827   52.01
db file scattered read                              6,105        6,264   30.09
direct path write                                   1,992        3,268   15.70
control file parallel write                           893          198     .95
db file parallel write                                 40          131     .63
         -------------------------------------------------------------        
 
Statistic                                    Total   per Second    per Trans 
--------------------------------- ---------------- ------------ ------------ 
CPU used by this session                   358,806        130.5     12,372.6 
parse time cpu                                  38          0.0          1.3 
recursive cpu usage                        186,636         67.9      6,435.7 

From these figures we can obtain:

 

(助记:

Response Time = Service Time + Wait Time(no Idle wait time)

CPU Other = Service Time - CPU Parse - CPU Recursive

)

*       Wait Time = 10,827 x 100% / 52.01% = 20,817 cs (任意等待时间Time (cs)/百分比Wt Time)

*       Service Time = 358,806 cs (Service Time = CPU used by this session)

*       Response Time = 358,806 + 20,817 = 379,623 cs

*       CPU Other = 358,806 - 38 - 186,636 = 172,132 cs

If we now calculate percentages for the top Response Time components:

*       CPU Other = 45.34%

*       CPU Recursive = 49.16%

*       direct path read = 2.85%

*       etc. etc.

CPU Other is a significant component of Response Time, so a possible next step is to look at the SQL ordered by Gets section.

 

 

各部分响应时间计算 (Oracle9i Release 2 & above)

Starting with Oracle9i Release 2, Statspack presents Service Time (obtained from the statistic CPU used by this session ) together with the top Wait Events in a section called Top 5 Timed Events, which replaces the section Top 5 Wait Events of previous releases.

Here is an example:

 
Top 5 Timed Events
~~~~~~~~~~~~~~~~~~                                                     % Total
Event                                               Waits    Time (s) Ela Time
-------------------------------------------- ------------ ----------- --------
library cache lock                                    141         424    76.52
db file scattered read                              3,367          96    17.40
CPU time                                                           32     5.79
db file sequential read                               161           1      .18
control file parallel write                           245           0      .05
          -------------------------------------------------------------
 
Statistic                                      Total     per Second    per Trans
--------------------------------- ------------------ -------------- ------------
CPU used by this session                       3,211            4.3      1,605.5
parse time cpu                                    59            0.1         29.5
recursive cpu usage                              232            0.3        116.0

These figures give us directly the percentages of the Wait Events against the total Response Time so no further calculations are necessary to assess the impact of Wait Events. Service Time is presented as CPU time in this section and corresponds to the total CPU utilisation. We can drill down to the various components of Service Time as follows:

*       Service Time = 3,211 cs (Service Time = CPU used by this session)

(助记:

Response Time = Service Time + Wait Time(no Idle wait time)

CPU Other = Service Time - CPU Parse - CPU Recursive

)

*       CPU Other = 3,211 - 59 - 232 = 2,920 cs

*       CPU Other = 2,920 / 3,211 x 5.79% = 5.26% (注意最后乘上CPU time%)

*       CPU Parse = 59 / 3,211 x 5.79% = 0.11%

*       CPU Recursive = 232 / 3,211 x 5.79% = 0.42%

In this example, the main performance problem was an issue related to the Library Cache.
The second most important time consumer was waiting for physical I/O due to multiblock reads (db file scattered read).
In this case a possible approach would be to look at the SQL ordered by Reads section of Statspack.

 

statspack 简单执行过程

1、建立PERFSTAT用户。为执行诊断做准备。(sys用户下)

Sql>@?/rdbms/admin/spcreate

2、执行诊断数据收集存储过程。(perfstat用户下)

Sql>exec statspack.snap;(间隔40分钟左右再执行一次)

3、生成诊断报表(perfstat用户下)

Sql>@?/rdbms/admin/spreport

 

例子报告计算结果:

*       (cpu other: 26/71*46.81%=17.14%

*         cpu parse:15/71*46.81%=9.9%

*         cpu recursive   30/71*46.81%=19.78%)

 

 

2.4 CBO优化器简介

CBO优化器由以下三部分组成

l        SQL转换

l        执行计划选择

l        代价模型与统计

 

1. SQL转换

SQL转换提供两种方向

 

1. 高性能目标

       试探性的,启发式的SQL转换

 例:

 简单视图综合

 CREATE VIEW TEST_VIEW AS

 SELECT ENAME,DNAME,SAL FROM EMP E,DEPT D

 WHERE E.DEPTNO=D.DEPTNO

 

 SELECT ENAME,DNAME FROM TEST_VIEW WHERE SAL>10000;

 

 优化器会将上面关联视图的查询转换为先将WHERE子句并入视图中。达到提高性能的作用。

    CREATE VIEW TEST_VIEW AS

 SELECT ENAME,DNAME,SAL FROM EMP E,DEPT D

 WHERE E.DEPTNO=D.DEPTNO AND SAL>10000

 

 SELECT ENAME,DNAME FROM TEST_VIEW;

 

2.基于代价的转换

 

2.1 物化视图重写

在表上建立物化视图,CBO遇到查询该表的SQL时,将SQL转换为查询相应的物化视图。

例:(SCOTT用户下)

1)

CREATE MATERIALIZED VIEW EMP_MV

ENABALE QUERY REWRITE AS

SELECT EMPNO,ENAME,DNAME FROM EMP,DEPT

WHERE EMP.DEPTNO=DEPT.DEPTNO;

 

2)

ALTER SESSION SET QUERY_REWRITE_ENABLED=TRUE;

ALTER SESSION SET OPTIMIZER_MODE=FIRST_ROWS_1;

 --COST 优化器

 

3)

@?/rdbms/admin/utlxplan

Explain plan for select empno,ename,dname from emp,dept where emp.deptno=dept.deptno;

@?/rdbms/admin/utlxpls

--在解释计划中可以看到查询用到的是EMP_MV

 

2.2 “OR”谓词

自动将“OR”谓词转换为UNION ALL

SELECT * FROM SHIPMENT, PORT P1, PORT P2

WHERE SHIPMENT.SOURCE_PORT_ID = P1.PORT_ID

AND SHIPMENT.DESTINATION_PORT_ID = P2.PORT_ID

AND (P1.PORT_NAME = 'OAKLAND' OR P2.PORT_NAME = 'OAKLAND')

 

上面的查询被转换成:

SELECT * FROM SHIPMENT, PORT P1, PORT P2

WHERE SHIPMENT.SOURCE_PORT_ID = P1.PORT_ID

AND SHIPMENT.DESTINATION_PORT_ID = P2.PORT_ID

AND P1.PORT_NAME = 'OAKLAND'

UNION ALL

SELECT * FROM SHIPMENT, PORT P1, PORT P2

WHERE SHIPMENT.SOURCE_PORT_ID = P1.PORT_ID

AND SHIPMENT.DESTINATION_PORT_ID = P2.PORT_ID

AND P2.PORT_NAME = 'OAKLAND' AND P1.PORT_NAME <> 'OAKLAND'

 

2.3  STAR 转换

STAR模式是数据集市、数据仓库的主要建模方式。一个STAR模式一般包含一个或多个大表,这些表叫做FACT表。还有众多的LOOKUP表叫维表(DIMENSION TABLE),存储说明性数据。ORACLE9i支持一种叫“STAR Transformation”的技术。优化针对STAR SCHEMAS的查询计算。

 

SELECT STORE.STATE, SUM(SALES.AMOUNT)

FROM SALES, DAY, QUARTER, PRODUCT, STORE

WHERE SALES.DAY_ID = DAY.DAY_ID AND DAY.QUARTER_ID =

QUARTER.QUARTER_ID

AND SALES.PRODUCT_ID = PRODUCT.PRODUCT_ID

AND SALES.STORE_ID = STORE.STORE_ID

AND PRODUCT.PRODUCT_CATEGORY = 'BEVERAGES'

AND QUARTER.QUARTER_NAME = '2001Q3'

GROUP BY STORE.STATE

 

上面的查询被转换成:

SELECT STORE.STATE, SUM(SALES.AMOUNT) FROM SALES, STORE

WHERE SALES.STORE_ID = STORE.STORE_ID

AND SALES.DAY_ID IN

(SELECT DAY.DAY_ID FROM DAY, QUARTER

WHERE DAY.QUARTER_ID = QUARTER.QUARTER_ID

AND QUARTER.QUARTER_NAME = '2001Q3')

AND SALES.PRODUCT_ID IN

(SELECT PRODUCT.PRODUCT_ID FROM PRODUCT

WHERE PRODUCT.PRODUCT_CATEGORY = 'BEVERAGES')

GROUP BY STORE.STATE

 

2. 执行计划选择

ORACLE的执行计划选择算法是可信赖的,是久经考验的。因为:

1、ORACLE有丰富的数据结构类型,优化器有充分的选择性。

   表、索引、分区等。

 

2、ORACLE领先的查询计算技术。

   例如,一个5表连接就有5!种连接顺序,10表连接就有超过300万种的连接顺序。ORACLE在处理这样的多表连接时,靠的是一个富有智慧的理解力,而不是机械地强制算法。这些理解力包括很多先进的技术,如,利用以往的优化成果;通过平衡优化时间与执行时间的比率预先砍掉大部分查询分析等。

 

ORACLE集存取路径选择与代价模型组合为一个完整的技术。

 

3. 代价模式与统计

(略)

 

* 2.5传统的SQL优化方法

1. 选用适合的ORACLE优化器

 

ORACLE的优化器共有3种:

 

a. RULE (基于规则) b. COST (基于成本) c. CHOOSE (选择性)

 

设置缺省的优化器,可以通过对init.ora文件中OPTIMIZER_MODE参数的各种声明如RULE,COST,CHOOSE,ALL_ROWS,FIRST_ROWS。你当然也在SQL句级或是会话(session)级对其进行覆盖。

 

为了使用基于成本的优化器(CBO, Cost-Based Optimizer) , 你必须经常运行analyze 命令,以增加数据库中的对象统计信息(object statistics)的准确性。

 

如果数据库的优化器模式设置为选择性(CHOOSE),那么实际的优化器模式将和是否运行过analyze命令有关. 如果table已经被analyze过, 优化器模式将自动成为CBO , 反之,数据库将采用RULE形式的优化器。

 

在缺省情况下,ORACLE采用CHOOSE优化器, 为了避免那些不必要的全表扫描(full table scan) , 你必须尽量避免使用CHOOSE优化器,而直接采用基于规则或者基于成本的优化器。

 

2. 访问Table的方式

 

ORACLE 采用两种访问表中记录的方式:

 

a. 全表扫描

 

全表扫描就是顺序地访问表中每条记录。ORACLE采用一次读入多个数据块(database block)的方式优化全表扫描。

 

b. 通过ROWID访问表

 

你可以采用基于ROWID的访问方式情况,提高访问表的效率,ROWID包含了表中记录的物理位置信息。ORACLE采用索引(INDEX)实现了数据和存放数据的物理位置(ROWID)之间的联系。通常索引提供了快速访问ROWID的方法,因此那些基于索引列的查询就可以得到性能上的提高。

 

3. 共享SQL语句

 

为了不重复解析相同的SQL语句,在第一次解析之后, ORACLE将SQL语句存放在内存中。这块位于系统全局区域SGA(system global area)的共享池(shared buffer pool)中的内存可以被所有的数据库用户共享。因此,当你执行一个SQL语句(有时被称为一个游标)时,如果它和之前的执行过的语句完全相同, ORACLE就能很快获得已经被解析的语句以及最好的执行路径。ORACLE的这个功能大大地提高了SQL的执行性能并节省了内存的使用。

 

这里需要注明的是,ORACLE对两者采取的是一种严格匹配,要达成共享,SQL语句必须完全相同(包括空格,换行等)。

 

共享的语句必须满足三个条件:

 

A. 字符级的比较:

 

当前被执行的语句和共享池中的语句必须完全相同.

 

例如:

 

SELECT * FROM EMP;

 

和下列每一个都不同

 

SELECT * from EMP;

 

Select * From Emp;

 

SELECT * FROM EMP;

 

B. 两个语句所指的对象必须完全相同:

 

例如:

 

用户 对象名 如何访问

 

Jack sal_limit private synonym

 

Work_city public synonym

 

Plant_detail public synonym

 

Jill sal_limit private synonym

 

Work_city public synonym

 

Plant_detail table owner

 

考虑一下下列SQL语句能否在这两个用户之间共享.

 

SQL

能否共享原因

 

select max(sal_cap) from sal_limit;

不能

每个用户都有一个private synonym - sal_limit , 它们是不同的对象

 

select count(*0 from work_city where sdesc like 'NEW%';

两个用户访问相同的对象public synonym - work_city

 

select a.sdesc,b.location from work_city a , plant_detail b where a.city_id = b.city_id

不能

用户jack 通过private synonym访问plant_detail 而jill 是表的所有者,对象不同.

 

C. 两个SQL语句中必须使用相同的名字的绑定变量(bind variables)

 

例如:

 

第一组的两个SQL语句是相同的(可以共享),而第二组中的两个语句是不同的(即使在运行时,赋于不同的绑定变量相同的值)

 

a.

select pin , name from people where pin = :blk1.pin;

select pin , name from people where pin = :blk1.pin;

 

b.

select pin , name from people where pin = :blk1.ot_ind;

select pin , name from people where pin = :blk1.ov_ind;

 

4. 选择最有效率的表名顺序(RBO)

 

ORACLE的解析器按照从右到左的顺序处理FROM子句中的表名,因此FROM子句中写在最后的表(基础表 driving table)将被最先处理。在FROM子句中包含多个表的情况下,你必须选择记录条数最少的表作为基础表。当ORACLE处理多个表时,会运用排序及合并的方式连接它们。首先,扫描第一个表(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)作为基础表, 交叉表是指那个被其他表所引用的表.

 

例如:

EMP表描述了LOCATION表和CATEGORY表的交集.

 

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更有效率

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

 

5. WHERE子句中的连接顺序

 

ORACLE采用自下而上的顺序解析WHERE子句,根据这个原理,表之间的连接必须写在其他WHERE条件之前, 那些可以过滤掉最大数量记录的条件必须写在WHERE子句的末尾。

 

例如:

(低效,执行时间156.3秒)

SELECT …

FROM EMP E

WHERE SAL > 50000

AND JOB = ‘MANAGER'

AND 25 < (SELECT COUNT(*) FROM EMP

WHERE MGR=E.EMPNO);

 

(高效,执行时间10.6秒)

SELECT …

FROM EMP E

WHERE 25 < (SELECT COUNT(*) FROM EMP

WHERE MGR=E.EMPNO)

AND SAL > 50000

AND JOB = ‘MANAGER';

 

6. SELECT子句中避免使用“*”

 

当你想在SELECT子句中列出所有的COLUMN时,使用动态SQL列引用 “*” 是一个方便的方法.不幸的是,这是一个非常低效的方法。实际上,ORACLE在解析的过程中, 会将'*' 依次转换成所有的列名, 这个工作是通过查询数据字典完成的, 这意味着将耗费更多的时间。

 

 

 

7. 减少访问数据库的次数

 

当执行每条SQL语句时, ORACLE在内部执行了许多工作: 解析SQL语句, 估算索引的利用率, 绑定变量 , 读数据块等等. 由此可见, 减少访问数据库的次数 , 就能实际上减少ORACLE的工作量.

 

例如,

以下有三种方法可以检索出雇员号等于0342或0291的职员.

 

方法1 (最低效)

SELECT EMP_NAME , SALARY , GRADE

FROM EMP

WHERE EMP_NO = 342;

 

SELECT EMP_NAME , SALARY , GRADE

FROM EMP

WHERE EMP_NO = 291;

 

方法2 (次低效)

 

DECLARE

CURSOR C1 (E_NO NUMBER) IS

SELECT EMP_NAME,SALARY,GRADE

FROM EMP

WHERE EMP_NO = E_NO;

BEGIN

OPEN C1(342);

FETCH C1 INTO …,..,.. ;

…..

OPEN C1(291);

FETCH C1 INTO …,..,.. ;

CLOSE C1;

END;

 

 

方法3 (高效)

SELECT A.EMP_NAME , A.SALARY , A.GRADE,

B.EMP_NAME , B.SALARY , B.GRADE

FROM EMP A,EMP B

WHERE A.EMP_NO = 342

AND B.EMP_NO = 291;

 

注意:

在SQL*Plus , SQL*Forms和Pro*C中重新设置ARRAYSIZE参数, 可以增加每次数据库访问的检索数据量 ,建议值为200

 

8. 使用DECODE函数来减少处理时间

 

使用DECODE函数可以避免重复扫描相同记录或重复连接相同的表.

 

例如:

SELECT COUNT(*),SUM(SAL)

FROM EMP

WHERE DEPT_NO = 0020

AND ENAME LIKE ‘SMITH%';

 

SELECT COUNT(*),SUM(SAL)

FROM EMP

WHERE DEPT_NO = 0030

AND ENAME LIKE ‘SMITH%';

 

你可以用DECODE函数高效地得到相同结果

 

SELECT COUNT(DECODE(DEPT_NO,0020,'X',NULL)) D0020_COUNT,

COUNT(DECODE(DEPT_NO,0030,'X',NULL)) D0030_COUNT,

SUM(DECODE(DEPT_NO,0020,SAL,NULL)) D0020_SAL,

SUM(DECODE(DEPT_NO,0030,SAL,NULL)) D0030_SAL

FROM EMP WHERE ENAME LIKE ‘SMITH%';

 

类似的,DECODE函数也可以运用于GROUP BY 和ORDER BY子句中.

 

9. 整合简单,无关联的数据库访问

 

如果你有几个简单的数据库查询语句,你可以把它们整合到一个查询中(即使它们之间没有关系)

 

例如:

SELECT NAME

FROM EMP

WHERE EMP_NO = 1234;

 

SELECT NAME

FROM DPT

WHERE DPT_NO = 10 ;

 

SELECT NAME

FROM CAT

WHERE CAT_TYPE = ‘RD';

 

上面的3个查询可以被合并成一个:

 

SELECT E.NAME , D.NAME , C.NAME

FROM CAT C , DPT D , EMP E,DUAL X

WHERE NVL(‘X',X.DUMMY) = NVL(‘X',E.ROWID(+))

AND NVL(‘X',X.DUMMY) = NVL(‘X',D.ROWID(+))

AND NVL(‘X',X.DUMMY) = NVL(‘X',C.ROWID(+))

AND E.EMP_NO(+) = 1234

AND D.DEPT_NO(+) = 10

AND C.CAT_TYPE(+) = ‘RD';

 

(注: 虽然采取这种方法,效率得到提高,但是程序的可读性大大降低,所以读者 还是要权衡之间的利弊)

 

10. 删除重复记录

 

最高效的删除重复记录方法 ( 因为使用了ROWID)

DELETE FROM EMP E

WHERE E.ROWID > (SELECT MIN(X.ROWID)

FROM EMP X

WHERE X.EMP_NO = E.EMP_NO);

 

11. 用TRUNCATE替代DELETE

 

当删除表中的记录时,在通常情况下, 回滚段(rollback segments ) 用来存放可以被恢复的信息. 如果你没有COMMIT事务,ORACLE会将数据恢复到删除之前的状态(准确地说是恢复到执行删除命令之前的状况)

 

而当运用TRUNCATE时, 回滚段不再存放任何可被恢复的信息.当命令运行后,数据不能被恢复.因此很少的资源被调用,执行时间也会很短。

 

12. 尽量多使用COMMIT

 

只要有可能,在程序中尽量多使用COMMIT,这样程序的性能得到提高,需求也会因为COMMIT所释放的资源而减少:

 

COMMIT所释放的资源:

 

a. 回滚段上用于恢复数据的信息.

 

b. 被程序语句获得的锁

 

c. redo log buffer 中的空间

 

d. ORACLE为管理上述3种资源中的内部花费

 

13. 计算记录条数

 

和一般的观点相反, count(*) 比count(1)稍快 , 当然如果可以通过索引检索,对索引列的计数仍旧是最快的. 例如 COUNT(EMPNO)

 

14. 用Where子句替换HAVING子句

 

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

 

例如:

 

低效:

SELECT REGION,AVG(LOG_SIZE)

FROM LOCATION

GROUP BY REGION

HAVING REGION REGION != ‘SYDNEY'

AND REGION != ‘PERTH'

 

 

高效

SELECT REGION,AVG(LOG_SIZE)

FROM LOCATION

WHERE REGION REGION != ‘SYDNEY'

AND REGION != ‘PERTH'

GROUP BY REGION

 

15. 减少对表的查询

 

在含有子查询的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)

 

高效

SELECT TAB_NAME

FROM TABLES

WHERE (TAB_NAME,DB_VER)

= ( SELECT TAB_NAME,DB_VER)

FROM TAB_COLUMNS

WHERE VERSION = 604)

 

Update 多个Column 例子:

 

低效:

UPDATE EMP

SET EMP_CAT = (SELECT MAX(CATEGORY) FROM EMP_CATEGORIES),

SAL_RANGE = (SELECT MAX(SAL_RANGE) FROM EMP_CATEGORIES)

 

WHERE EMP_DEPT = 0020;

 

高效:

UPDATE EMP

SET (EMP_CAT, SAL_RANGE)

= (SELECT MAX(CATEGORY) , MAX(SAL_RANGE)

FROM EMP_CATEGORIES)

WHERE EMP_DEPT = 0020;

 

16. 通过内部函数提高SQL效率.

 

SELECT H.EMPNO,E.ENAME,H.HIST_TYPE,T.TYPE_DESC,COUNT(*)

FROM HISTORY_TYPE T,EMP E,EMP_HISTORY H

WHERE H.EMPNO = E.EMPNO

AND H.HIST_TYPE = T.HIST_TYPE

GROUP BY H.EMPNO,E.ENAME,H.HIST_TYPE,T.TYPE_DESC;

 

通过调用下面的函数可以提高效率.

FUNCTION LOOKUP_HIST_TYPE(TYP IN NUMBER) RETURN VARCHAR2

AS

TDESC VARCHAR2(30);

 

CURSOR C1 IS

SELECT TYPE_DESC

FROM HISTORY_TYPE

WHERE HIST_TYPE = TYP;

 

BEGIN

OPEN C1;

FETCH C1 INTO TDESC;

CLOSE C1;

RETURN (NVL(TDESC,'?'));

END;

 

FUNCTION LOOKUP_EMP(EMP IN NUMBER) RETURN VARCHAR2

AS

ENAME VARCHAR2(30);

 

CURSOR C1 IS

SELECT ENAME

FROM EMP

WHERE EMPNO=EMP;

 

BEGIN

OPEN C1;

FETCH C1 INTO ENAME;

CLOSE C1;

RETURN (NVL(ENAME,'?'));

END;

 

SELECT H.EMPNO,LOOKUP_EMP(H.EMPNO),

H.HIST_TYPE,LOOKUP_HIST_TYPE(H.HIST_TYPE),COUNT(*)

FROM EMP_HISTORY H

GROUP BY H.EMPNO , H.HIST_TYPE;

 

17. 使用表的别名(Alias)

 

当在SQL语句中连接多个表时, 请使用表的别名并把别名前缀于每个Column上。这样一来,就可以减少解析的时间并减少那些由Column歧义引起的语法错误。

 

18. 用EXISTS替代IN

 

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

 

低效:

SELECT *

FROM EMP (基础表)

WHERE EMPNO > 0

AND DEPTNO IN (SELECT DEPTNO

FROM DEPT

WHERE LOC = ‘MELB')

 

高效:

SELECT *

FROM EMP (基础表)

WHERE EMPNO > 0

AND EXISTS (SELECT ‘X'

FROM DEPT

WHERE DEPT.DEPTNO = EMP.DEPTNO

AND LOC = ‘MELB')

 

19. 用NOT EXISTS替代NOT IN

 

在子查询中,NOT IN子句将执行一个内部的排序和合并。无论在哪种情况下,NOT IN都是最低效的 (因为它对子查询中的表执行了一个全表遍历). 为了避免使用NOT IN ,我们可以把它改写成外连接(Outer Joins)或NOT EXISTS。

 

例如:

SELECT …

FROM EMP

WHERE DEPT_NO NOT IN (SELECT DEPT_NO

FROM DEPT

WHERE DEPT_CAT='A');

 

为了提高效率.改写为:

(方法一: 高效)

SELECT ….

FROM EMP A,DEPT B

WHERE A.DEPT_NO = B.DEPT(+)

AND B.DEPT_NO IS NULL

AND B.DEPT_CAT(+) = ‘A'

 

(方法二: 最高效)

SELECT ….

FROM EMP E

WHERE NOT EXISTS (SELECT ‘X'

FROM DEPT D

WHERE D.DEPT_NO = E.DEPT_NO

AND DEPT_CAT = ‘A');

 

20. 用表连接替换EXISTS

 

通常来说 ,采用表连接的方式比EXISTS更有效率

 

SELECT ENAME

FROM EMP E

WHERE EXISTS (SELECT ‘X'

FROM DEPT

WHERE DEPT_NO = E.DEPT_NO

AND DEPT_CAT = ‘A');

 

(更高效)

SELECT ENAME

FROM DEPT D,EMP E

WHERE E.DEPT_NO = D.DEPT_NO

AND DEPT_CAT = ‘A' ;

 

21. 用EXISTS替换DISTINCT

 

当提交一个包含一对多表信息(比如部门表和雇员表)的查询时,避免在SELECT子句中使用DISTINCT. 一般可以考虑用EXIST替换

 

例如:

 

低效:

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);

 

EXISTS 使查询更为迅速,因为RDBMS核心模块将在子查询的条件一旦满足后,立刻返回结果。

 

*23. 使用TKPROF 工具来查询SQL性能状态

 

SQL trace 工具收集正在执行的SQL的性能状态数据并记录到一个跟踪文件中. 这个跟踪文件提供了许多有用的信息,例如解析次数.执行次数,CPU使用时间等.这些数据将可以用来优化你的系统。

 

 

设置SQL TRACE在会话级别: 有效

 

 

ALTER SESSION SET SQL_TRACE TRUE

 

 

设置SQL TRACE 在整个数据库有效仿, 你必须将SQL_TRACE参数在init.ora中设为TRUE, USER_DUMP_DEST参数说明了生成跟踪文件的目录

 

 

(译者按: 这一节中,作者并没有提到TKPROF的用法, 对SQL TRACE的用法也不够准确, 设置SQL TRACE首先要在init.ora中设定TIMED_STATISTICS, 这样才能得到那些重要的时间状态. 生成的trace文件是不可读的,所以要用TKPROF工具对其进行转换,TKPROF有许多执行参数. 大家可以参考ORACLE手册来了解具体的配置. )

 

*24. 用EXPLAIN PLAN 分析SQL语句

 

 

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

 

你需要按照从里到外,从上到下的次序解读分析的结果. EXPLAIN PLAN分析的结果是用缩进的格式排列的, 最内部的操作将被最先解读, 如果两个操作处于同一层中,带有最小操作号的将被首先执行.

 

NESTED LOOP是少数不按照上述规则处理的操作, 正确的执行路径是检查对NESTED LOOP提供数据的操作,其中操作号最小的将被最先处理.

 

25. 用索引提高效率

 

索引是表的一个概念部分,用来提高检索数据的效率。实际上,ORACLE使用了一个复杂的自平衡B-tree结构。通常,通过索引查询数据比全表扫描要快。当ORACLE找出执行查询和Update语句的最佳路径时,ORACLE优化器将使用索引。同样在联结多个表时使用索引也可以提高效率。另一个使用索引的好处是,它提供了主键(primary key)的唯一性验证。

 

除了那些LONG或LONG RAW数据类型, 你可以索引几乎所有的列。通常, 在大型表中使用索引特别有效。当然,你也会发现, 在扫描小表时,使用索引同样能提高效率。

 

虽然使用索引能得到查询效率的提高,但是我们也必须注意到它的代价。索引需要空间来存储,也需要定期维护, 每当有记录在表中增减或索引列被修改时, 索引本身也会被修改. 这意味着每条记录的INSERT , DELETE , UPDATE将为此多付出4 , 5 次的磁盘I/O . 因为索引需要额外的存储空间和处理,那些不必要的索引反而会使查询反应时间变慢.

 

注:

定期的重构索引是有必要的.

ALTER INDEX <INDEXNAME> REBUILD <TABLESPACENAME>

 

26. 索引的操作

 

ORACLE对索引有两种访问模式.

 

索引唯一扫描 ( INDEX UNIQUE SCAN)

 

大多数情况下, 优化器通过WHERE子句访问INDEX.

 

例如:

表LODGING有两个索引 : 建立在LODGING列上的唯一性索引LODGING_PK和建立在MANAGER列上的非唯一性索引LODGING$MANAGER.

 

SELECT *

FROM LODGING

WHERE LODGING = ‘ROSE HILL';

 

在内部,上述SQL将被分成两步执行, 首先 , LODGING_PK 索引将通过索引唯一扫描的方式被访问 , 获得相对应的ROWID, 通过ROWID访问表的方式 执行下一步检索。

 

如果被检索返回的列包括在INDEX列中,ORACLE将不执行第二步的处理(通过ROWID访问表). 因为检索数据保存在索引中, 单单访问索引就可以完全满足查询结果.

 

下面SQL只需要INDEX UNIQUE SCAN 操作.

 

SELECT LODGING

FROM LODGING

WHERE LODGING = ‘ROSE HILL';

 

索引范围查询(INDEX RANGE SCAN)

 

适用于两种情况:

 

1. 基于一个范围的检索

2. 基于非唯一性索引的检索

 

例1:

SELECT LODGING

FROM LODGING

WHERE LODGING LIKE ‘M%';

 

WHERE子句条件包括一系列值, ORACLE将通过索引范围查询的方式查询LODGING_PK . 由于索引范围查询将返回一组值, 它的效率就要比索引唯一扫描低一些.

 

例2:

SELECT LODGING

FROM LODGING

WHERE MANAGER = ‘BILL GATES';

 

这个SQL的执行分两步, LODGING$MANAGER的索引范围查询(得到所有符合条件记录的ROWID) 和下一步同过ROWID访问表得到LODGING列的值. 由于LODGING$MANAGER是一个非唯一性的索引,数据库不能对它执行索引唯一扫描.

 

由于SQL返回LODGING列,而它并不存在于LODGING$MANAGER索引中, 所以在索引范围查询后会执行一个通过ROWID访问表的操作.

 

WHERE子句中, 如果索引列所对应的值的第一个字符由通配符(WILDCARD)开始, 索引将不被采用.

 

SELECT LODGING

FROM LODGING

WHERE MANAGER LIKE ‘%HANMAN';

 

在这种情况下,ORACLE将使用全表扫描.

 

27. 基础表的选择

 

基础表(Driving Table)是指被最先访问的表(通常以全表扫描的方式被访问). 根据优化器的不同, SQL语句中基础表的选择是不一样的.

 

如果你使用的是CBO (COST BASED OPTIMIZER),优化器会检查SQL语句中的每个表的物理大小,索引的状态,然后选用花费最低的执行路径.

 

如果你用RBO (RULE BASED OPTIMIZER) , 并且所有的连接条件都有索引对应, 在这种情况下, 基础表就是FROM 子句中列在最后的那个表.

 

举例:

SELECT A.NAME , B.MANAGER

FROM WORKER A,

LODGING B

WHERE A.LODGING = B.LODING;

 

由于LODGING表的LODING列上有一个索引, 而且WORKER表中没有相比较的索引, WORKER表将被作为查询中的基础表。

 

28. 多个平等的索引

 

当SQL语句的执行路径可以使用分布在多个表上的多个索引时, ORACLE会同时使用多个索引并在运行时对它们的记录进行合并,检索出仅对全部索引有效的记录。

 

在ORACLE选择执行路径时,唯一性索引的等级高于非唯一性索引. 然而这个规则只有当WHERE子句中索引列和常量比较才有效.如果索引列和其他表的索引类相比较. 这种子句在优化器中的等级是非常低的。

 

如果不同表中两个想同等级的索引将被引用, FROM子句中表的顺序将决定哪个会被率先使用. FROM子句中最后的表的索引将有最高的优先级。

 

如果相同表中两个想同等级的索引将被引用, WHERE子句中最先被引用的索引将有最高的优先级。

 

举例:

DEPTNO上有一个非唯一性索引,EMP_CAT也有一个非唯一性索引。

SELECT ENAME,

FROM EMP

WHERE DEPT_NO = 20

AND EMP_CAT = ‘A';

 

这里,DEPTNO索引将被最先检索,然后同EMP_CAT索引检索出的记录进行合并. 执行路径如下:

 

TABLE ACCESS BY ROWID ON EMP

AND-EQUAL

INDEX RANGE SCAN ON DEPT_IDX

INDEX RANGE SCAN ON CAT_IDX

 

29. 等式比较和范围比较

 

当WHERE子句中有索引列, ORACLE不能合并它们,ORACLE将用范围比较.

 

举例:

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

 

30. 不明确的索引等级

 

当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

 

31. 强制索引失效

 

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

 

举例:

SELECT ENAME

FROM EMP

WHERE EMPNO = 7935

AND DEPTNO + 0 = 10 /*DEPTNO上的索引将失效*/

AND EMP_TYPE || ‘' = ‘A' /*EMP_TYPE上的索引将失效*/

 

这是一种相当直接的提高查询效率的办法. 但是你必须谨慎考虑这种策略,一般来说,只有在你希望单独优化几个SQL时才能采用它.

 

这里有一个例子关于何时采用这种策略,

 

假设在EMP表的EMP_TYPE列上有一个非唯一性的索引而EMP_CLASS上没有索引.

 

SELECT ENAME

FROM EMP

WHERE EMP_TYPE = ‘A'

AND EMP_CLASS = ‘X';

 

优化器会注意到EMP_TYPE上的索引并使用它. 这是目前唯一的选择. 如果,一段时间以后, 另一个非唯一性建立在EMP_CLASS上,优化器必须对两个索引进行选择,在通常情况下,优化器将使用两个索引并在他们的结果集合上执行排序及合并. 然而,如果其中一个索引(EMP_TYPE)接近于唯一性而另一个索引(EMP_CLASS)上有几千个重复的值. 排序及合并就会成为一种不必要的负担. 在这种情况下,你希望使优化器屏蔽掉EMP_CLASS索引.

 

用下面的方案就可以解决问题.

SELECT ENAME

FROM EMP

WHERE EMP_TYPE = ‘A'

AND EMP_CLASS||'' = ‘X';

 

32. 避免在索引列上使用计算.

 

WHERE子句中,如果索引列是函数的一部分.优化器将不使用索引而使用全表扫描.

 

举例:

低效:

SELECT …

FROM DEPT

WHERE SAL * 12 > 25000;

 

高效:

SELECT …

FROM DEPT

WHERE SAL > 25000/12;

 

33. 自动选择索引

 

如果表中有两个以上(包括两个)索引,其中有一个唯一性索引,而其他是非唯一性.在这种情况下,ORACLE将使用唯一性索引而完全忽略非唯一性索引.

 

举例:

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

 

35. 用>=替代>

 

如果DEPTNO上有一个索引,

 

高效:

SELECT *

FROM EMP

WHERE DEPTNO >=4

 

低效:

SELECT *

FROM EMP

WHERE DEPTNO >3

 

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

 

36. 用UNION替换OR (适用于索引列)

 

通常情况下, 用UNION替换WHERE子句中的OR将会起到较好的效果. 对索引列使用OR将造成全表扫描. 注意, 以上规则只针对多个索引列有效. 如果有column没有被索引, 查询效率可能会因为你没有选择OR而降低。

 

在下面的例子中, LOC_ID 和REGION上都建有索引。

 

高效:

SELECT LOC_ID , LOC_DESC , REGION

FROM LOCATION

WHERE LOC_ID = 10

UNION

SELECT LOC_ID , LOC_DESC , REGION

FROM LOCATION

WHERE REGION = “MELBOURNE

 

低效:

SELECT LOC_ID , LOC_DESC , REGION

FROM LOCATION

WHERE LOC_ID = 10 OR REGION = “MELBOURNE

 

如果你坚持要用OR, 那就需要返回记录最少的索引列写在最前面.

 

注意:

WHERE KEY1 = 10 (返回最少记录)

OR KEY2 = 20 (返回最多记录)

 

ORACLE 内部将以上转换为

WHERE KEY1 = 10 AND

((NOT KEY1 = 10) AND KEY2 = 20)

 

38. 避免在索引列上使用IS NULL和IS NOT NULL

 

避免在索引中使用任何可以为空的列,ORACLE将无法使用该索引。对于单列索引,如果列包含空值,索引中将不存在此记录. 对于复合索引,如果每个列都为空,索引中同样不存在此记录。如果至少有一个列不为空,则记录存在于索引中。

 

举例:

如果唯一性索引建立在表的A列和B列上, 并且表中存在一条记录的A,B值为(123,null) , ORACLE将不接受下一条具有相同A,B值(123,null)的记录(插入)。然而如果所有的索引列都为空,ORACLE将认为整个键值为空。因此你可以插入1000条具有相同键值的记录,当然它们都是空!

 

因为空值不存在于索引列中,所以WHERE子句中对索引列进行空值比较将使ORACLE停用该索引。

 

举例:

低效: (索引失效)

SELECT …

FROM DEPARTMENT

WHERE DEPT_CODE IS NOT NULL;

 

高效: (索引有效)

SELECT …

FROM DEPARTMENT

WHERE DEPT_CODE >=0;

 

39. 总是使用索引的第一个列

 

如果索引是建立在多个列上, 只有在它的第一个列(leading column)被where子句引用时,优化器才会选择使用该索引.

 

41. 用UNION-ALL 替换UNION ( 如果有可能的话)

 

当SQL语句需要UNION两个查询结果集合时,这两个结果集合会以UNION-ALL的方式被合并, 然后在输出最终结果前进行排序。

如果用UNION ALL替代UNION, 这样排序就不是必要了。效率就会因此得到提高.

 

举例:

 

低效:

SELECT ACCT_NUM, BALANCE_AMT

FROM DEBIT_TRANSACTIONS

WHERE TRAN_DATE = '31-DEC-95'

UNION

SELECT ACCT_NUM, BALANCE_AMT

FROM DEBIT_TRANSACTIONS

WHERE TRAN_DATE = '31-DEC-95'

 

高效:

SELECT ACCT_NUM, BALANCE_AMT

FROM DEBIT_TRANSACTIONS

WHERE TRAN_DATE = '31-DEC-95'

UNION ALL

SELECT ACCT_NUM, BALANCE_AMT

FROM DEBIT_TRANSACTIONS

WHERE TRAN_DATE = '31-DEC-95'

 

注:

需要注意的是,UNION ALL 将重复输出两个结果集合中相同记录. 因此各位还是要从业务需求分析使用UNION ALL的可行性。

 

UNION 将对结果集合排序,这个操作会使用到SORT_AREA_SIZE这块内存。对于这块内存的优化也是相当重要的。

 

42. 使用提示(Hints)

 

对于表的访问,可以使用两种Hints。FULL 和 ROWID 。

 

FULL hint 告诉ORACLE使用全表扫描的方式访问指定表.

 

例如:

SELECT /*+ FULL(EMP) */ *

FROM EMP

WHERE EMPNO = 7893;

 

ROWID hint 告诉ORACLE使用TABLE ACCESS BY ROWID的操作访问表。

 

通常, 你需要采用TABLE ACCESS BY ROWID的方式特别是当访问大表的时候, 使用这种方式, 你需要知道ROIWD的值或者使用索引。

 

如果一个大表没有被设定为缓存(CACHED)表而你希望它的数据在查询结束是仍然停留在SGA中,你就可以使用CACHE hint 来告诉优化器把数据保留在SGA中。通常CACHE hint 和 FULL hint 一起使用.

 

例如:

SELECT /*+ FULL(WORKER) CACHE(WORKER)*/ *

FROM WORK;

 

索引hint 告诉ORACLE使用基于索引的扫描方式. 你不必说明具体的索引名称

例如:

SELECT /*+ INDEX(LODGING) */ LODGING

FROM LODGING

WHERE MANAGER = ‘BILL GATES';

 

在不使用hint的情况下, 以上的查询应该也会使用索引,然而,如果该索引的重复值过多而你的优化器是CBO, 优化器就可能忽略索引. 在这种情况下, 你可以用INDEX hint强制ORACLE使用该索引.

 

ORACLE hints 还包括ALL_ROWS, FIRST_ROWS, RULE,USE_NL, USE_MERGE, USE_HASH 等等.

 

注:

使用hint ,表示我们对ORACLE优化器缺省的执行路径不满意,需要手工修改。

这是一个很有技巧性的工作。我建议只针对特定的,少数的SQL进行hint的优化。对ORACLE的优化器还是要有信心(特别是CBO)

 

43. 用WHERE替代ORDER BY

 

ORDER BY 子句只在两种严格的条件下使用索引。

 

ORDER BY中所有的列必须包含在相同的索引中并保持在索引中的排列顺序.

 

ORDER BY中所有的列必须定义为非空.

 

WHERE子句使用的索引和ORDER BY子句中所使用的索引不能并列.

 

例如:

表DEPT包含以下列:

DEPT_CODE PK NOT NULL

DEPT_DESC NOT NULL

DEPT_TYPE NULL

非唯一性的索引(DEPT_TYPE)

 

低效: (索引不被使用)

SELECT DEPT_CODE

FROM DEPT

ORDER BY DEPT_TYPE

 

EXPLAIN PLAN:

SORT ORDER BY

TABLE ACCESS FULL

 

高效: (使用索引)

SELECT DEPT_CODE

FROM DEPT

WHERE DEPT_TYPE > 0

 

EXPLAIN PLAN:

 

TABLE ACCESS BY ROWID ON EMP

INDEX RANGE SCAN ON DEPT_IDX

 

44. 避免改变索引列的类型.

 

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

 

假设 EMPNO是一个数值类型的索引列.

SELECT …

FROM EMP

WHERE EMPNO = ‘123'

 

实际上,经过ORACLE类型转换, 语句转化为:

SELECT …

FROM EMP

WHERE EMPNO = TO_NUMBER(‘123')

 

幸运的是,类型转换没有发生在索引列上,索引的用途没有被改变.

 

现在,假设EMP_TYPE是一个字符类型的索引列.

SELECT …

FROM EMP

WHERE EMP_TYPE = 123

 

这个语句被ORACLE转换为:

SELECT …

FROM EMP

WHERE TO_NUMBER(EMP_TYPE)=123

 

因为内部发生的类型转换, 这个索引将不会被用到!

 

45. 需要当心的WHERE子句

 

某些SELECT 语句中的WHERE子句不使用索引. 这里有一些例子.

 

在下面的例子里,‘!=' 将不使用索引。记住,索引只能告诉你什么存在于表中, 而不能告诉你什么不存在于表中。

 

不使用索引:

SELECT ACCOUNT_NAME

FROM TRANSACTION

WHERE AMOUNT !=0;

 

使用索引:

SELECT ACCOUNT_NAME

FROM TRANSACTION

WHERE AMOUNT >0;

 

下面的例子中, ‘||'是字符连接函数. 就象其他函数那样, 停用了索引.

 

不使用索引:

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';

 

下面的例子中, ‘+'是数学函数. 就象其他数学函数那样, 停用了索引.

 

不使用索引:

SELECT ACCOUNT_NAME, AMOUNT

FROM TRANSACTION

WHERE AMOUNT + 3000 >5000;

 

使用索引:

SELECT ACCOUNT_NAME, AMOUNT

FROM TRANSACTION

WHERE AMOUNT > 2000 ;

 

下面的例子中,相同的索引列不能互相比较,这将会启用全表扫描.

 

不使用索引:

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,'%');

 

注:

如果一定要对使用函数的列启用索引, ORACLE新的功能: 基于函数的索引(Function-Based Index) 也许是一个较好的方案.

 

CREATE INDEX EMP_I ON EMP (UPPER(ename)); /*建立基于函数的索引*/

 

SELECT * FROM emp WHERE UPPER(ename) = ‘BLACKSNAIL'; /*将使用索引*/

 

48. 避免使用耗费资源的操作

 

带有DISTINCT,UNION,MINUS,INTERSECT,ORDER BY的SQL语句会启动SQL引擎执行耗费资源的排序(SORT)功能。DISTINCT需要一次排序操作, 而其他的至少需要执行两次排序。

 

例如,一个UNION查询,其中每个查询都带有GROUP BY子句, GROUP BY会触发嵌入排序(NESTED SORT) ; 这样, 每个查询需要执行一次排序, 然后在执行UNION时, 又一个唯一排序(SORT UNIQUE)操作被执行而且它只能在前面的嵌入排序结束后才能开始执行. 嵌入的排序的深度会大大影响查询的效率。

 

通常, 带有UNION, MINUS , INTERSECT的SQL语句都可以用其他方式重写。

 

注:

如果你的数据库的SORT_AREA_SIZE调配得好, 使用UNION , MINUS, INTERSECT也是可以考虑的, 毕竟它们的可读性很强

 

53. 分离表和索引

 

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

 

(完)

posted @ 2006-04-17 11:27  zhanlh  阅读(1480)  评论(0编辑  收藏  举报