高效SQL语句必杀技

No SQL,No cost. SQL语句是造成数据库开销最大的部分。而不良SQL写法直接导致数据库系统性能下降的情形比比皆是。那么如何才能称得
上高效的SQL语句呢?一是查询优化器为当前的SQL语句生成最佳的执行计划,保证数据读写使用最佳路径;二是设置合理的物理存储结构,如表
的类型,字段的顺序,字段的数据类型等。本文主要描述如何编写高效的SQL语句并给出示例。下面的描述主要分为三个部分,一是编写高效SQL
语句,二是使用索引提高查询性能的部分,三是总结部分。

一、编写高效SQL语句

  1. 1) 选择最有效的表名顺序(仅适用于RBO模式)                                                                             
  2.     ORACLE的解析器总是按照从右到左的顺序处理FROM子句中的表名,因此FROM子句中最后的一个表将作为驱动表被优先处理。当FROM子句      
  3. 存在多个表的时候,应当考虑将表上记录最少的那个表置于FROM的最右端作为基表。Oracle会首先扫描基表(FROM子句中最后的那个表)并对      
  4. 记录进行排序,然后扫描第二个表(FROM子句中最后第二个表),最后将所有从第二个表中检索出的记录与第一个表中合适记录进行合并。如      
  5. 果有3个以上的表连接查询, 那就需要选择交叉表(intersection table)作为基础表,交叉表是指那个被其他表所引用的表。                
  6.                                                                                                                                 
  7. 下面的例子使用最常见的scott或hr模式下的表进行演示                               
  8.                                                                                               
  9. 表 EMP 有14条记录                                                               
  10. 表 DEPT 有4条记录                                                  
  11. SELECT  /*+rule */COUNT( * ) FROM   emp, dept;         --高效的写法   
  12.                                                                                                                                 
  13. scott@CNMMBO> set autotrace traceonly stat;                                                    
  14. scott@CNMMBO> SELECT  /*+rule */COUNT( * ) FROM   emp, dept;                       
  15.                                                                             
  16. Elapsed: 00:00:00.14                                                        
  17.                                                                                     
  18. Statistics                                                                        
  19. ----------------------------------------------------------                              
  20.           1  recursive calls                                                           
  21.           0  db block gets                                                            
  22.          35  consistent gets                                                       
  23.           0  physical reads                                                
  24.           0  redo size                                                              
  25.         515  bytes sent via SQL*Net to client                                   
  26.         492  bytes received via SQL*Net from client                            
  27.           2  SQL*Net roundtrips to/from client                           
  28.           0  sorts (memory)                                                      
  29.           0  sorts (disk)                                                            
  30.           1  rows processed                                                                  
  31.                                                                                             
  32. SELECT  /*+rule */COUNT( * ) FROM   dept, emp;        --低效的写法           
  33. scott@CNMMBO> SELECT  /*+rule */COUNT( * ) FROM   dept, emp;                            
  34.                                                                                          
  35. Elapsed: 00:00:00.02                                                                          
  36.                                          
  37. Statistics                                                                                  
  38. ----------------------------------------------------------                              
  39.           1  recursive calls                                                        
  40.           0  db block gets                                                               
  41.         105  consistent gets                                                             
  42.           0  physical reads                                                             
  43.           0  redo size                                                                
  44.         515  bytes sent via SQL*Net to client                                   
  45.         492  bytes received via SQL*Net from client                                   
  46.           2  SQL*Net roundtrips to/from client                                          
  47.           0  sorts (memory)                                                          
  48.           0  sorts (disk)                                                          
  49.           1  rows processed                                                                
  50.                                                                                              
  51. 2) select 查询中避免使用'*'                                                              
  52.    当你想在SELECT子句中列出所有的COLUMN时,使用动态SQL列引用'*' 是一个方便的方法.不幸的是,这是一个非常低效的方法.实际      
  53. 上,ORACLE在解析的过程中, 会将 '*' 依次转换成所有的列名, 这个工作是通过查询数据字典完成的, 这意味着将耗费更多的时间。        
  54. 注:本文中的例子出于简化演示而使用了select * ,生产环境应避免使用.                               
  55.                                                                                  
  56. 3) 减少访问数据库的次数                                                  
  57.     每当执行一条SQL语句,Oracle 需要完成大量的内部操作,象解析SQL语句,估算索引的利用率,绑定变量, 读数据块等等.由此可      
  58. 见,减少访问数据库的次数,实际上是降低了数据库系统开销                             
  59. -->下面通过3种方式来获得雇员编号为7788与7902的相关信息                         
  60.                                                                             
  61. -->方式 1 (最低效):                                                     
  62. select ename,job,salfrom empwhere empno=7788;                           
  63.                                                                             
  64. select ename,job,salfrom empwhere empno=7902;                                   
  65.                                                                       
  66. -->方式 2 (次低效):                                                            
  67. -->下面使用了参数游标来完成,每传递一次参数则需要对表emp访问一次,增加了I/O                         
  68.   DECLARE                                                                             
  69.     CURSOR C1(E_NO NUMBER) IS                                                    
  70.     SELECT ename, job, sal                                                         
  71.     FROM emp                                                                   
  72.     WHERE empno = E_NO;                                                     
  73.   BEGIN                                                                       
  74.     OPEN C1 (7788);                                                          
  75.     FETCH C1INTO …, …, …;                                                     
  76.     ..                                                                    
  77.     OPEN C1 (7902);                                                    
  78.     FETCH C1INTO …, …, …;                                                  
  79.     CLOSE C1;                                                               
  80.   END;                                                                          
  81.                                                                                  
  82. -->方式 3 (最高效)                                           
  83. SELECT a.ename                                                             
  84.      , a.job                                                    
  85.      , a.sal                                                       
  86.      , b.ename                                                       
  87.      , b.job                                                          
  88.      , b.sal                                                       
  89. FROM   emp a, emp b                                                     
  90. WHERE  a.empno = 7788OR b.empno = 7902;                                     
  91.                                                        
  92. 注意:在SQL*Plus,SQL*Forms和Pro*C中重新设置ARRAYSIZE参数,可以增加每次数据库访问的检索数据量,建议值为200.      
  93.                                                                 
  94. 4) 使用DECODE函数来减少处理时间                               
  95. -->使用decode函数可以避免重复扫描相同的行或重复连接相同的表                 
  96. selectcount(*),sum(sal)from empwhere deptno=20and enamelike 'SMITH%';             
  97.                                                                                          
  98. selectcount(*),sum(sal)from empwhere deptno=30and enamelike 'SMITH%';               
  99.                                                                                              
  100. -->通过使用decode函数一次扫描即可完成所有满足条件记录的处理                                      
  101. SELECTCOUNT( DECODE( deptno, 20,'x', NULL ) ) d20_count                                        
  102.      , COUNT( DECODE( deptno, 30,'x', NULL ) ) d30_count                                     
  103.      , SUM( DECODE( deptno, 20, sal,NULL ) ) d20_sal                                         
  104.      , SUM( DECODE( deptno, 30, sal,NULL ) ) d30_sal                                           
  105. FROM   emp                                                                 
  106. WHERE  enameLIKE 'SMITH%';                                               
  107.                                                                          
  108. 类似的,DECODE函数也可以运用于GROUPBYORDERBY子句中。                          
  109.                                                                                 
  110. 5) 整合简单,无关联的数据库访问                                       
  111. -->如果你有几个简单的数据库查询语句,你可以把它们整合到一个查询中以提高性能(即使它们之间没有关系)        
  112. -->整合前                                                                         
  113. SELECTname                                                                           
  114. FROM   emp                                                                         
  115. WHERE  empno = 1234;                                                             
  116.                                                                               
  117. SELECTname                                                                    
  118. FROM   dept                                                         
  119. WHERE  deptno = 10;                                                       
  120.                                                                                            
  121. SELECTname                                                                
  122. FROM   cat                                                              
  123. WHERE  cat_type ='RD';                                                                  
  124.                                                                                          
  125. -->整合后                                                                     
  126. SELECT e.name, d.name, c.name                                                                    
  127. FROM   cat c                                                                                        
  128.      , dpt d                                                                                       
  129.      , emp e                                                                                       
  130.      , dual x                                                                                    
  131. WHERE      NVL('X', x.dummy ) = NVL('X', e.ROWID(+) )                         
  132.        AND NVL('X', x.dummy ) = NVL('X', d.ROWID(+) )          
  133.        AND NVL('X', x.dummy ) = NVL('X', c.ROWID(+) )           
  134.        AND e.emp_no(+) = 1234                                                                    
  135.        AND d.dept_no(+) = 10                                                                    
  136.        AND c.cat_type(+) ='RD';                                                                  
  137.                                                                                         
  138. -->从上面的SQL语句可以看出,尽管三条语句被整合为一条,性能得以提高,然可读性差,此时应权衡性能与代价            
  139.                                                                                                                                 
  140. 6) 删除重复记录                                                                                  
  141. -->通过使用rowid来作为过滤条件,性能高效                                             
  142. DELETEFROM emp e                                                                    
  143. WHERE  e.ROWID > (SELECTMIN( x.ROWID )    
  144.                   FROM   emp x                                                      
  145.                   WHERE  x.empno = e.empno);                                                  
  146.                                                                                    
  147. 7) 使用truncate 代替delete                                                              
  148. -->通常情况下,任意记录的删除需要在回滚段构造删除前镜像以实现回滚(rollback).对于未提交的数据在执行rollback之后,Oracle会生成   
  149. -->等价SQL语句去恢复记录(如delete,则生成对应的insert语句;如insert则生成对应的delete;如update,则是同时生成delete和insert 
  150. -->使用truncate命令则是执行DDL命令,不产生任何回滚信息,直接格式化并释放高水位线.故该语句性能高效.由于不能rollback,因此慎用. 
  151.                                                                                                 
  152. 8) 尽量多使用COMMIT(COMMIT应确保事务的完整性)            
  153. -->只要有可能,在程序中尽量多使用COMMIT,这样程序的性能得到提高,需求也会因为COMMIT所释放的资源而减少            
  154. -->COMMIT所释放的资源:                                                                                                        
  155. -->1.回滚段上用于恢复数据的信息                                                                                                
  156. -->2.释放语句处理期间所持有的锁                                                                                                
  157. -->3.释放redo log buffer占用的空间(commit将redo log buffer中的entries 写入到联机重做日志文件)                        
  158. -->4.ORACLE为管理上述3种资源中的内部开销                                                                                       
  159.                                                                                                                                 
  160. 9) 计算记录条数                                                                                                                 
  161. -->一般的情况下,count(*)比count(1)稍快.如果可以通过索引检索,对索引列的计数是最快的,因为直接扫描索引即可,例如COUNT(EMPNO) 
  162. -->实际情况是经测试上述三种情况并无明显差异.                                                 
  163.                                                                                                
  164. 10) 用Where子句替换HAVING子句                                                                                                   
  165. -->尽可能的避免having子句,因为HAVING 子句是对检索出所有记录之后再对结果集进行过滤。这个处理需要排序,总计等操作               
  166. -->通过WHERE子句则在分组之前即可过滤不必要的记录数目,从而减少聚合的开销                                                       
  167.                                                                                                                                 
  168. -->低效:                                                                              
  169. SELECT deptno,AVG( sal )                                                                
  170. FROM   emp                                                                                             
  171. GROUPBY deptno                                                                                         
  172. HAVING deptno = 20;                                                                                         
  173.                                                                                            
  174. scott@CNMMBO> SELECT deptno,AVG( sal )                                                                     
  175.   2  FROM   emp                                                                                      
  176.   3  GROUPBY deptno                                                                                 
  177.   4  HAVING deptno= 20;                                                                                  
  178.                                                                                              
  179. Statistics                     
  180. ----------------------------------------------------------   
  181.           0  recursive calls                                                                          
  182.           0  db block gets                                                                
  183.           7  consistent gets                                                              
  184.           0  physical reads                                                               
  185.           0  redo size                                                                      
  186.         583  bytes sent via SQL*Net to client                                                 
  187.         492  bytes received via SQL*Net from client                                                
  188.           2  SQL*Net roundtrips to/from client                                                      
  189.           0  sorts (memory)                                                                     
  190.           0  sorts (disk)                                                                    
  191.           1  rows processed                                                               
  192. -->高效:                                                              
  193. SELECT deptno,AVG( sal )         
  194. FROM   emp                                                                                     
  195. WHERE  deptno = 20                                                                                 
  196. GROUPBY deptno;                                                                  
  197.                                                                                      
  198. scott@CNMMBO> SELECT deptno,AVG( sal )                 
  199.   2  FROM   emp                                                                            
  200.   3  WHERE  deptno = 20                                                                  
  201.   4  GROUPBY deptno;                                                                  
  202.                                                                                         
  203. Statistics                                         
  204. ----------------------------------------------------------      
  205.           0  recursive calls                                                             
  206.           0  db block gets                                                              
  207.           2  consistent gets                                                              
  208.           0  physical reads                                                           
  209.           0  redo size                                                               
  210.         583  bytes sent via SQL*Net to client                                        
  211.         492  bytes received via SQL*Net from client                                     
  212.           2  SQL*Net roundtrips to/from client                                              
  213.           0  sorts (memory)                                                                     
  214.           0  sorts (disk)                                                                         
  215.           1  rows processed                                                                    
  216.                                                                                        
  217. 11) 最小化表查询次数                                                                                                            
  218. -->在含有子查询的SQL语句中,要特别注意减少对表的查询                                                                           
  219. -->低效:                                                         
  220. SELECT *                                                                                     
  221. FROM   employees                                                                                  
  222. WHERE  department_id = (SELECT department_id                                                      
  223.                         FROM   departments                                                    
  224.                         WHERE  department_name ='Marketing')                                  
  225.        AND manager_id = (SELECT manager_id                                                      
  226.                          FROM   departments                                                    
  227.                          WHERE  department_name ='Marketing');                                 
  228. -->高效:                                                              
  229. SELECT *                                                                                   
  230. FROM   employees                                                                            
  231. WHERE  ( department_id, manager_id ) = (SELECT department_id, manager_id                                 
  232.                                         FROM   departments                                                  
  233.                                         WHERE  department_name ='Marketing')                  
  234.                                                                                    
  235. -->类似更新多列的情形              
  236. -->低效:                   
  237. UPDATE employees                                                                                 
  238. SET    job_id = (SELECTMAX( job_id )FROM jobs ), salary = (SELECTAVG( min_salary )FROM jobs )          
  239. WHERE  department_id = 10;                                                                   
  240.                                                                                        
  241. -->高效:                
  242. UPDATE employees          
  243. SET    ( job_id, salary ) = (SELECTMAX( job_id ),AVG( min_salary )FROM jobs )      
  244. WHERE  department_id = 10;                                                             
  245.                                                                          
  246. 12) 使用表别名                                                                       
  247. -->在多表查询时,为所返回列使用表别名作为前缀以减少解析时间以及那些相同列歧义引起的语法错误                                    
  248.                                                                                 
  249. 13) 用EXISTS替代IN                                                                                  
  250.     在一些基于基础表的查询中,为了满足一个条件,往往需要对另一个表进行联接.在这种情况下,使用EXISTS(或NOT EXISTS)通常       
  251. 将提高查询的效率.                                                                                                              
  252. -->低效:                               
  253. SELECT *                                                             
  254. FROM   emp                                                      
  255. WHERE  sal > 1000                                                     
  256.        AND deptnoIN (SELECT deptno                                        
  257.                       FROM   dept                                         
  258.                       WHERE  loc ='DALLAS')                                    
  259.                                                                   
  260. -->高效:                                                                             
  261. SELECT *                                                                
  262. FROM   emp                                                            
  263. WHERE  empno > 1000                                         
  264.        AND EXISTS                                                      
  265.               (SELECT 1                                      
  266.                FROM   dept                                    
  267.                WHERE  deptno = emp.deptnoAND loc ='DALLAS')                          
  268.                     
  269. 14) 用NOT EXISTS替代NOTIN    
  270.     在子查询中,NOTIN子句引起一个内部的排序与合并.因此,无论何时NOTIN子句都是最低效的,因为它对子查询中的表执行了一个全表       
  271. 遍历.为避免该情形,应当将其改写成外部连接(OUTTER JOIN)或适用NOT EXISTS                                   
  272. -->低效:                                                                    
  273. SELECT *                                                                                       
  274. FROM   emp                                                                       
  275. WHERE  deptnoNOT IN (SELECT deptno                                         
  276.                        FROM   dept                                           
  277.                        WHERE  loc ='DALLAS');                                            
  278.                                    
  279. -->高效:                                           
  280. SELECT e.*                                                                                     
  281. FROM   emp e                                                                                     
  282. WHERE NOT EXISTS                                                                                    
  283.           (SELECT 1                                                                                   
  284.            FROM   dept                                                                               
  285.            WHERE  deptno = e.deptnoAND loc ='DALLAS');                                      
  286.                                                                      
  287. -->最高效(尽管下面的查询最高效,并不推荐使用,因为列loc使用了不等运算,当表dept数据量较大,且loc列存在索引的话,则此时索引失效) 
  288. SELECT e.*                                                                                    
  289. FROM   emp eLEFT JOIN dept d ON e.deptno = d.deptno                                                
  290. WHERE  d.loc <>'DALLAS'                                                                      
  291.                                                            
  292. 15) 使用表连接替换EXISTS                                               
  293. 一般情况下,使用表连接比EXISTS更高效                                                            
  294. -->低效:                                                  
  295. SELECT *                                                                                                      
  296. FROM   employees e                                                                                        
  297. WHERE  EXISTS                                                                                                     
  298.           (SELECT 1                                                                      
  299.            FROM   departments                                                             
  300.            WHERE  department_id = e.department_idAND department_name ='IT');                                
  301.                                                                                    
  302. -->高效:                
  303. SELECT *             -->经测试此写法SQLplus下比上面的写法多一次逻辑读,而在Toad下两者结果一致             
  304. FROM   employees eINNERJOIN departments dON d.department_id = e.department_id             
  305. WHERE  d.department_name ='IT';                                                       
  306.                                                              
  307. 16) 用EXISTS替换DISTINCT         
  308. 对于一对多关系表信息查询时(如部门表和雇员表),应避免在select 子句中使用distinct,而使用exists来替换          
  309.                                                     
  310. -->低效:                                                              
  311. SELECTDISTINCT e.department_id, department_name                                             
  312. FROM   departments dINNERJOIN employees eON d.department_id = e.department_id;                  
  313.                                  
  314. -->高效:                                                          
  315. SELECT d.department_id,department_name                                                        
  316. from departments d                                                                      
  317. WHERE  EXISTS                                                                             
  318.           (SELECT 1                                                                     
  319.            FROM   employees e                                                      
  320.            WHERE  d.department_id=e.department_id);                                             
  321.                                                                      
  322. EXISTS 使查询更为迅速,因为RDBMS核心模块将在子查询的条件一旦满足后,立刻返回结果                               
  323. -->经测试此写法SQLplus下比上面的写法多一次逻辑读,而在Toad下两者结果一致                                
  324.                                                              
  325. 17) 使用 UNIONALL 替换 UNION(如果有可能的话)                                                          
  326. 当SQL语句需要UNION两个查询结果集时,这两个结果集合会以UNION-ALL的方式被合并, 然后在输出最终结果前进行排序。      
  327. 如果用UNIONALL替代UNION, 这样排序就不是必要了。 效率就会因此得到提高。                                                        
  328.                                                                
  329. 注意:                      
  330. UNIONALL会输出所有的结果集,而UNION则过滤掉重复记录并对其进行排序.因此在使用时应考虑业务逻辑是否允许当前的结果集存在重复现象  
  331.                                                                         
  332. 寻找低效的SQL语句                                                           
  333. -->下面的语句主要适用于从视图v$sqlarea中获得当前运行下且耗用buffer_gets较多的SQL语句                 
  334. SELECT executions                                                                    
  335.      , disk_reads                                                                   
  336.      , buffer_gets                                                                 
  337.      , ROUND( ( buffer_gets        
  338.                - disk_reads )      
  339.              / buffer_gets, 2 )     
  340.           hit_ratio                                     
  341.      , ROUND( disk_reads / executions, 2 ) reads_per_run                  
  342.      , sql_text                                      
  343. FROM   v$sqlarea                                                              
  344. WHERE      executions > 0                                                  
  345.        AND buffer_gets > 0                                              
  346.        AND ( buffer_gets                                                   
  347.             - disk_reads )                                                 
  348.            / buffer_gets < 0.80                                                       
  349. ORDERBY 4DESC;                                                  
  350.                              
  351. 18) 尽可能避免使用函数,函数会导致更多的 recursive calls

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值