高效SQL语句必杀技

No SQL,No cost. SQL语句是造成数据库开销最大的部分。而不良SQL写法直接导致数据库系统性能下降的情形比比皆是。那么如何才能称得

上高效的SQL语句呢?一是查询优化器为当前的SQL语句生成最佳的执行计划,保证数据读写使用最佳路径;二是设置合理的物理存储结构,如表

的类型,字段的顺序,字段的数据类型等。本文主要描述如何编写高效的SQL语句并给出示例。下面的描述主要分为三个部分,一是编写高效SQL

语句,二是使用索引提高查询性能的部分,三是总结部分。

1) 选择最有效的表名顺序(仅适用于RBO模式)

ORACLE的解析器总是按照从右到左的顺序处理FROM子句中的表名,因此FROM子句中最后的一个表将作为驱动表被优先处理。当FROM子句

存在多个表的时候,应当考虑将表上记录最少的那个表置于FROM的最右端作为基表。Oracle会首先扫描基表(FROM子句中最后的那个表)并对

记录进行排序,然后扫描第二个表(FROM子句中最后第二个表),最后将所有从第二个表中检索出的记录与第一个表中合适记录进行合并。如

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

下面的例子使用最常见的scott或hr模式下的表进行演示

表 EMP 有14条记录

表 DEPT 有4条记录

SELECT  /*+ rule */ COUNT( * )  FROM   emp, dept;          --高效的写法

scott@CNMMBO> set autotrace traceonly stat;

scott@CNMMBO> SELECT  /*+ rule */ COUNT( * )  FROM   emp, dept;

Elapsed: 00:00:00.14

Statistics

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

1  recursive calls

0  db block gets

35  consistent gets

0  physical reads

0  redo size

515  bytes sent via SQL*Net to client

492  bytes received via SQL*Net from client

2  SQL*Net roundtrips to/from client

0  sorts (memory)

0  sorts (disk)

1  rows processed

SELECT  /*+ rule */ COUNT( * )  FROM   dept, emp;         --低效的写法

scott@CNMMBO> SELECT  /*+ rule */ COUNT( * )  FROM   dept, emp;

Elapsed: 00:00:00.02

Statistics

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

1  recursive calls

0  db block gets

105  consistent gets

0  physical reads

0  redo size

515  bytes sent via SQL*Net to client

492  bytes received via SQL*Net from client

2  SQL*Net roundtrips to/from client

0  sorts (memory)

0  sorts (disk)

1  rows processed

2) select 查询中避免使用'*'

当你想在SELECT子句中列出所有的COLUMN时,使用动态SQL列引用 '*' 是一个方便的方法.不幸的是,这是一个非常低效的方法.实际

上,ORACLE在解析的过程中, 会将 '*' 依次转换成所有的列名, 这个工作是通过查询数据字典完成的, 这意味着将耗费更多的时间。

注:本文中的例子出于简化演示而使用了select * ,生产环境应避免使用.

3) 减少访问数据库的次数

每当执行一条SQL语句,Oracle 需要完成大量的内部操作,象解析SQL语句,估算索引的利用率,绑定变量, 读数据块等等.由此可

见,减少访问数据库的次数,实际上是降低了数据库系统开销

-->下面通过3种方式来获得雇员编号为7788与7902的相关信息

-->方式 1 (最低效):

select ename,job,sal from emp where empno=7788;

select ename,job,sal from emp where empno=7902;

-->方式 2 (次低效):

-->下面使用了参数游标来完成,每传递一次参数则需要对表emp访问一次,增加了I/O

DECLARE

CURSOR C1(E_NO NUMBER)  IS

SELECT ename, job, sal

FROM emp

WHERE empno = E_NO;

BEGIN

OPEN C1 (7788);

FETCH C1 INTO …, …, …;

..

OPEN C1 (7902);

FETCH C1 INTO …, …, …;

CLOSE C1;

END;

-->方式 3 (最高效)

SELECT a.ename

, a.job

, a.sal

, b.ename

, b.job

, b.sal

FROM   emp a, emp b

WHERE  a.empno = 7788 OR b.empno = 7902;

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

4) 使用DECODE函数来减少处理时间

-->使用decode函数可以避免重复扫描相同的行或重复连接相同的表

select count(*),sum(sal) from emp where deptno=20 and ename like 'SMITH%';

select count(*),sum(sal) from emp where deptno=30 and ename like 'SMITH%';

-->通过使用decode函数一次扫描即可完成所有满足条件记录的处理

SELECT COUNT( DECODE( deptno, 20, 'x', NULL ) ) d20_count

, COUNT( DECODE( deptno, 30, 'x', NULL ) ) d30_count

, SUM( DECODE( deptno, 20, sal, NULL ) ) d20_sal

, SUM( DECODE( deptno, 30, sal, NULL ) ) d30_sal

FROM   emp

WHERE  ename LIKE 'SMITH%';

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

5) 整合简单,无关联的数据库访问

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

-->整合前

SELECT name

FROM   emp

WHERE  empno = 1234;

SELECT name

FROM   dept

WHERE  deptno = 10;

SELECT name

FROM   cat

WHERE  cat_type = 'RD';

-->整合后

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

-->从上面的SQL语句可以看出,尽管三条语句被整合为一条,性能得以提高,然可读性差,此时应权衡性能与代价

6) 删除重复记录

-->通过使用rowid来作为过滤条件,性能高效

DELETE FROM emp e

WHERE  e.ROWID > (SELECT MIN( x.ROWID )

FROM   emp x

WHERE  x.empno = e.empno);

7) 使用truncate 代替 delete

-->通常情况下,任意记录的删除需要在回滚段构造删除前镜像以实现回滚(rollback).对于未提交的数据在执行rollback之后,Oracle会生成

-->等价SQL语句去恢复记录(如delete,则生成对应的insert语句;如insert则生成对应的delete;如update,则是同时生成delete和insert

-->使用truncate命令则是执行DDL命令,不产生任何回滚信息,直接格式化并释放高水位线.故该语句性能高效.由于不能rollback,因此慎用.

8) 尽量多使用COMMIT(COMMIT应确保事务的完整性)

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

-->COMMIT所释放的资源:

-->1.回滚段上用于恢复数据的信息

-->2.释放语句处理期间所持有的锁

-->3.释放redo log buffer占用的空间(commit将redo log buffer中的entries 写入到联机重做日志文件)

-->4.ORACLE为管理上述3种资源中的内部开销

9) 计算记录条数

-->一般的情况下,count(*)比count(1)稍快.如果可以通过索引检索,对索引列的计数是最快的,因为直接扫描索引即可,例如COUNT(EMPNO)

-->实际情况是经测试上述三种情况并无明显差异.

10) 用Where子句替换HAVING子句

-->尽可能的避免having子句,因为HAVING 子句是对检索出所有记录之后再对结果集进行过滤。这个处理需要排序,总计等操作

-->通过WHERE子句则在分组之前即可过滤不必要的记录数目,从而减少聚合的开销

-->低效:

SELECT deptno, AVG( sal )

FROM   emp

GROUP BY deptno

HAVING deptno = 20;

scott@CNMMBO> SELECT deptno, AVG( sal )

2  FROM   emp

3  GROUP BY deptno

4  HAVING deptno= 20;

Statistics

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

0  recursive calls

0  db block gets

7  consistent gets

0  physical reads

0  redo size

583  bytes sent via SQL*Net to client

492  bytes received via SQL*Net from client

2  SQL*Net roundtrips to/from client

0  sorts (memory)

0  sorts (disk)

1  rows processed

-->高效:

SELECT deptno, AVG( sal )

FROM   emp

WHERE  deptno = 20

GROUP BY deptno;

scott@CNMMBO> SELECT deptno, AVG( sal )

2  FROM   emp

3  WHERE  deptno = 20

4  GROUP BY deptno;

Statistics

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

0  recursive calls

0  db block gets

2  consistent gets

0  physical reads

0  redo size

583  bytes sent via SQL*Net to client

492  bytes received via SQL*Net from client

2  SQL*Net roundtrips to/from client

0  sorts (memory)

0  sorts (disk)

1  rows processed

11) 最小化表查询次数

-->在含有子查询的SQL语句中,要特别注意减少对表的查询

-->低效:

SELECT *

FROM   employees

WHERE  department_id = (SELECT department_id

FROM   departments

WHERE  department_name = 'Marketing')

AND manager_id = (SELECT manager_id

FROM   departments

WHERE  department_name = 'Marketing');

-->高效:

SELECT *

FROM   employees

WHERE  ( department_id, manager_id ) = (SELECT department_id, manager_id

FROM   departments

WHERE  department_name = 'Marketing')

-->类似更新多列的情形

-->低效:

UPDATE employees

SET    job_id = ( SELECT MAX( job_id ) FROM jobs ), salary = ( SELECT AVG( min_salary ) FROM jobs )

WHERE  department_id = 10;

-->高效:

UPDATE employees

SET    ( job_id, salary ) = ( SELECT MAX( job_id ), AVG( min_salary ) FROM jobs )

WHERE  department_id = 10;

12) 使用表别名

-->在多表查询时,为所返回列使用表别名作为前缀以减少解析时间以及那些相同列歧义引起的语法错误

13) 用EXISTS替代IN

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

将提高查询的效率.

-->低效:

SELECT *

FROM   emp

WHERE  sal > 1000

AND deptno IN (SELECT deptno

FROM   dept

WHERE  loc = 'DALLAS')

-->高效:

SELECT *

FROM   emp

WHERE  empno > 1000

AND EXISTS

(SELECT 1

FROM   dept

WHERE  deptno = emp.deptno AND loc = 'DALLAS')

14) 用NOT EXISTS替代NOT IN

在子查询中,NOT IN子句引起一个内部的排序与合并.因此,无论何时NOT IN子句都是最低效的,因为它对子查询中的表执行了一个全表

遍历.为避免该情形,应当将其改写成外部连接(OUTTER JOIN)或适用NOT EXISTS

-->低效:

SELECT *

FROM   emp

WHERE  deptno NOT IN (SELECT deptno

FROM   dept

WHERE  loc = 'DALLAS');

-->高效:

SELECT e.*

FROM   emp e

WHERE  NOT EXISTS

(SELECT 1

FROM   dept

WHERE  deptno = e.deptno AND loc = 'DALLAS');

-->最高效(尽管下面的查询最高效,并不推荐使用,因为列loc使用了不等运算,当表dept数据量较大,且loc列存在索引的话,则此时索引失效)

SELECT e.*

FROM   emp e LEFT JOIN dept d ON e.deptno = d.deptno

WHERE  d.loc <> 'DALLAS'

15) 使用表连接替换EXISTS

一般情况下,使用表连接比EXISTS更高效

-->低效:

SELECT *

FROM   employees e

WHERE  EXISTS

(SELECT 1

FROM   departments

WHERE  department_id = e.department_id AND department_name = 'IT');

-->高效:

SELECT *              -->经测试此写法SQLplus下比上面的写法多一次逻辑读,而在Toad下两者结果一致

FROM   employees e INNER JOIN departments d ON d.department_id = e.department_id

WHERE  d.department_name = 'IT';

16) 用EXISTS替换DISTINCT

对于一对多关系表信息查询时(如部门表和雇员表),应避免在select 子句中使用distinct,而使用exists来替换

-->低效:

SELECT DISTINCT e.department_id, d.department_name

FROM   departments d INNER JOIN employees e ON d.department_id = e.department_id;

-->高效:

SELECT d.department_id,department_name

from departments d

WHERE  EXISTS

(SELECT 1

FROM   employees e

WHERE  d.department_id=e.department_id);

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

-->经测试此写法SQLplus下比上面的写法多一次逻辑读,而在Toad下两者结果一致

17) 使用 UNION ALL 替换 UNION(如果有可能的话)

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

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

注意:

UNION ALL会输出所有的结果集,而UNION则过滤掉重复记录并对其进行排序.因此在使用时应考虑业务逻辑是否允许当前的结果集存在重复现象

寻找低效的SQL语句

-->下面的语句主要适用于从视图v$sqlarea中获得当前运行下且耗用buffer_gets较多的SQL语句

SELECT executions

, disk_reads

, buffer_gets

, ROUND( ( buffer_gets

- disk_reads )

/ buffer_gets, 2 )

hit_ratio

, 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.80

ORDER BY 4 DESC;

18) 尽可能避免使用函数,函数会导致更多的 recursive calls

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,639评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,277评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,221评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,474评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,570评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,816评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,957评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,718评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,176评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,511评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,646评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,322评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,934评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,755评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,987评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,358评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,514评论 2 348

推荐阅读更多精彩内容