高效SQL語句必殺技

轉自:

http://blog.csdn.net/robinson_0612/article/details/7406672

 

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,sal from emp where empno=7788;                            
  63.                                                                              
  64. select ename,job,sal from emp where 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 C1 INTO …, …, …;                                                      
  76.     ..                                                                     
  77.     OPEN C1 (7902);                                                     
  78.     FETCH C1 INTO …, …, …;                                                   
  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 = 7788 OR b.empno = 7902;                                      
  91.                                                         
  92. 注意:在SQL*Plus,SQL*Forms和Pro*C中重新設置ARRAYSIZE參數,可以增加每次數據庫訪問的檢索數據量,建議值爲200.       
  93.                                                                  
  94. 4) 使用DECODE函數來減少處理時間                                
  95. -->使用decode函數可以避免重複掃描相同的行或重複連接相同的表                    
  96. select count(*),sum(sal) from emp where deptno=20 and ename like 'SMITH%';              
  97.                                                                                           
  98. select count(*),sum(sal) from emp where deptno=30 and ename like 'SMITH%';                
  99.                                                                                               
  100. -->通過使用decode函數一次掃描即可完成所有滿足條件記錄的處理                                         
  101. SELECT COUNT( 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  ename LIKE 'SMITH%';                                                
  107.                                                                           
  108. 類似的,DECODE函數也可以運用於GROUP BY 和ORDER BY子句中。                           
  109.                                                                                  
  110. 5) 整合簡單,無關聯的數據庫訪問                                        
  111. -->如果你有幾個簡單的數據庫查詢語句,你可以把它們整合到一個查詢中以提高性能(即使它們之間沒有關係)           
  112. -->整合前                                                                            
  113. SELECT name                                                                            
  114. FROM   emp                                                                          
  115. WHERE  empno = 1234;                                                              
  116.                                                                                
  117. SELECT name                                                                     
  118. FROM   dept                                                          
  119. WHERE  deptno = 10;                                                        
  120.                                                                                             
  121. SELECT name                                                                 
  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. DELETE FROM emp e                                                                     
  143. WHERE  e.ROWID > (SELECT MIN( 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. GROUP BY deptno                                                                                          
  172. HAVING deptno = 20;                                                                                          
  173.                                                                                             
  174. scott@CNMMBO> SELECT deptno, AVG( sal )                                                                      
  175.   2  FROM   emp                                                                                       
  176.   3  GROUP BY 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. GROUP BY deptno;                                                                   
  197.                                                                                       
  198. scott@CNMMBO> SELECT deptno, AVG( sal )                  
  199.   2  FROM   emp                                                                             
  200.   3  WHERE  deptno = 20                                                                   
  201.   4  GROUP BY 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 = ( SELECT MAX( job_id ) FROM jobs ), salary = ( SELECT AVG( min_salary ) FROM jobs )           
  239. WHERE  department_id = 10;                                                                    
  240.                                                                                         
  241. -->高效:                   
  242. UPDATE employees           
  243. SET    ( job_id, salary ) = ( SELECT MAX( 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 deptno IN (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.deptno AND loc = 'DALLAS')                           
  268.                      
  269. 14) 用NOT EXISTS替代NOT IN     
  270.     在子查詢中,NOT IN子句引起一個內部的排序與合併.因此,無論何時NOT IN子句都是最低效的,因爲它對子查詢中的表執行了一個全表        
  271. 遍歷.爲避免該情形,應當將其改寫成外部連接(OUTTER JOIN)或適用NOT EXISTS                                    
  272. -->低效:                                                                       
  273. SELECT *                                                                                        
  274. FROM   emp                                                                        
  275. WHERE  deptno NOT 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.deptno AND loc = 'DALLAS');                                       
  286.                                                                       
  287. -->最高效(儘管下面的查詢最高效,並不推薦使用,因爲列loc使用了不等運算,當表dept數據量較大,且loc列存在索引的話,則此時索引失效)    
  288. SELECT e.*                                                                                     
  289. FROM   emp e LEFT 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_id AND department_name = 'IT');                                 
  301.                                                                                     
  302. -->高效:                   
  303. SELECT *              -->經測試此寫法SQLplus下比上面的寫法多一次邏輯讀,而在Toad下兩者結果一致                
  304. FROM   employees e INNER JOIN departments d ON d.department_id = e.department_id              
  305. WHERE  d.department_name = 'IT';                                                        
  306.                                                               
  307. 16) 用EXISTS替換DISTINCT          
  308. 對於一對多關係表信息查詢時(如部門表和僱員表),應避免在select 子句中使用distinct,而使用exists來替換           
  309.                                                      
  310. -->低效:                                                                 
  311. SELECT DISTINCT e.department_id, department_name                                              
  312. FROM   departments d INNER JOIN employees e ON 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) 使用 UNION ALL 替換 UNION(如果有可能的話)                                                           
  326. 當SQL語句需要UNION兩個查詢結果集時,這兩個結果集合會以UNION-ALL的方式被合併, 然後在輸出最終結果前進行排序。       
  327. 如果用UNION ALL替代UNION, 這樣排序就不是必要了。 效率就會因此得到提高。                                                         
  328.                                                                 
  329. 注意:                       
  330. UNION ALL會輸出所有的結果集,而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. ORDER BY 4 DESC;                                                   
  350.                               
  351. 18) 儘可能避免使用函數,函數會導致更多的 recursive calls        

 

二、合理使用索引以提高性能
       索引依賴於表而存在,是真實表的一個縮影,類似於一本書的目錄,通過目錄以更快獲得所需的結果。Oracle使用了一個複雜的自平衡
B數據結構。即任意記錄的DML操作將打破索引的平衡,而定期重構索引使得索引重新獲得平衡。通常,通過索引查找數據比全表掃描更高效。
任意的DQL或DML操作,SQL優化引擎優先使用索引來計算當前操作的成本以生成最佳的執行計劃。一旦使用索引操出參數optimizer_index_cost_adj
設定的值才使用全表掃描。同樣對於多表連接使用索引也可以提高效率。同時索引也提供主鍵(primary key)的唯一性驗證。

       除了那些LONG或LONG RAW數據類型,你可以索引幾乎所有的列.通常,在大型表中使用索引特別有效.當然,你也會發現,在掃描小表時,使用索
引同樣能提高效率。

       雖然使用索引能得到查詢效率的提高,但是索引需要空間來存儲,需要定期維護.尤其是在有大量DML操作的表上,任意的DML操作都將引起索
引的變更這意味着每條記錄的INSERT , DELETE , UPDATE將爲此多付出4 , 5 次的磁盤I/O . 因爲索引需要額外的存儲空間和處理,
那些不必要的索引反而會使查詢反應時間變慢。

DML操作使用索引上存在碎片而失去高度均衡,因此定期的重構索引是有必要的.

  1. 1) 避免基於索引列的計算                                                                                                           
  2. where 子句中的謂詞上存在索引,而此時基於該列的計算將使得索引失效                                                                  
  3.                                                                                                                                   
  4. -->低效:                       
  5. SELECT employee_id, first_name                                                                                                    
  6. FROM   employees                                                                                                                  
  7. WHERE  employee_id + 10 > 150;        -->索引列上使用了計算,因此索引失效,走全表掃描方式                                          
  8.                                                                                                                                   
  9. -->高效:                               
  10. SELECT employee_id, first_name                                                                                                    
  11. FROM   employees                                                                                                                  
  12. WHERE  employee_id > 160;    -->走索引範圍掃描方式                                                                                 
  13.                          
  14. 例外情形      
  15. 上述規則不適用於SQL中的MINMAX函數                                                                                               
  16. hr@CNMMBO> SELECT MAX( employee_id ) max_id                                                                                       
  17.   2  FROM   employees                                                                                                             
  18.   3  WHERE  employee_id                                                                                                           
  19.   4         + 10 > 150;                                                                                                           
  20.                                                                                                                                   
  21. 1 row selected.                                                                                                                   
  22.                                                                                                                                   
  23. Execution Plan                                                                                                                    
  24. ----------------------------------------------------------          
  25. Plan hash value: 1481384439                                 
  26. ---------------------------------------------------------------------------------------------                 
  27. | Id  | Operation                   | Name          | Rows  | Bytes | Cost (%CPU)| Time     |                  
  28. ---------------------------------------------------------------------------------------------                  
  29. |   0 | SELECT STATEMENT            |               |     1 |     4 |     1   (0)| 00:00:01 |                  
  30. |   1 |  SORT AGGREGATE             |               |     1 |     4 |            |          |                   
  31. |   2 |   FIRST ROW                 |               |     5 |    20 |     1   (0)| 00:00:01 |           
  32. |*  3 |    INDEX FULL SCAN (MIN/MAX)| EMP_EMP_ID_PK |     5 |    20 |     1   (0)| 00:00:01 |          
  33. ---------------------------------------------------------------------------------------------               
  34.                                                                                                                                   
  35. 2) 避免在索引列上使用NOT運算或不等於運算(<>,!=)                                                                                   
  36. 通常,我們要避免在索引列上使用NOT或<>,兩者會產生在和在索引列上使用函數相同的影響。 當ORACLE遇到NOT或不等運算時,他就會停止        
  37. 使用索引轉而執行全表掃描。                                                                                                        
  38.                                                                                                                                   
  39. -->低效:                                                                                  
  40. SELECT *                                                                                                                          
  41. FROM   emp                                                                                                                        
  42. WHERE  NOT ( deptno = 20 );   -->實際上NOT ( deptno = 20 )等同於deptno <> 20,即deptno <>同樣會限制索引                             
  43.                                                                                                                                   
  44. -->高效:                                                                            
  45. SELECT *                                                                                                                          
  46. FROM   emp                                                                                                                        
  47. WHERE  deptno > 20 OR deptno < 20;                                                                                                
  48. -->儘管此方式可以替換且實現上述結果,但依然走全表掃描,如果是單純的 > 或 < 運算,則此時爲索引範圍掃描                              
  49.                                                                                                                                   
  50. 需要注意的是,在某些時候, ORACLE優化器會自動將NOT轉化成相對應的關係操作符                                                        
  51. 其次如果是下列運算符進行NOT運算,依然有可能選擇走索引, 僅僅除了NOT = 之外,因爲 NOT = 等價於 <>                                     
  52.                                                                                                                                   
  53. NOT >”   to <=                                                                                                                 
  54. NOT >=”  to <                                                                                                                  
  55. NOT <”   to >=                                                                                                                 
  56. NOT <=”  to >                                                                                                                  
  57.                                                                                                                                   
  58. 來看一個實際的例子                                                                                                                
  59. hr@CNMMBO> SELECT *                                                                                                               
  60.   2  FROM   employees                                                                                                             
  61.   3  where not employee_id<100; -->索引列上使用了not,但是該查詢返回了所有的記錄,即107條,因此此時選擇走全表掃描                   
  62.                                                                                                                                   
  63. 107 rows selected.                                                                                                                
  64.                                                                                                                                   
  65. Execution Plan                                                                                                                    
  66. ----------------------------------------------------------     
  67. Plan hash value: 1445457117                                                                                                       
  68. -------------------------------------------------------------------------------              
  69. | Id  | Operation         | Name      | Rows  | Bytes | Cost (%CPU)| Time     |          
  70. -------------------------------------------------------------------------------        
  71. |   0 | SELECT STATEMENT  |           |   107 |  7276 |     3   (0)| 00:00:01 |    
  72. |*  1 |  TABLE ACCESS FULL| EMPLOYEES |   107 |  7276 |     3   (0)| 00:00:01 | -->執行計劃中使用了走全表掃描方式         
  73. -------------------------------------------------------------------------------                                             
  74. Predicate Information (identified by operation id):                                                
  75. ---------------------------------------------------        
  76.        
  77.    1 - filter("EMPLOYEE_ID">=100)           -->查看這裏的謂詞信息被自動轉換爲 >= 運算符                     
  78.                                                                                                                                   
  79. hr@CNMMBO> SELECT *                                                                                                               
  80.   2  FROM   employees                                                                                                             
  81.   3  where not employee_id<140; -->此例與上面的語句相同,僅僅是查詢範圍不同返回67條記錄,而此時選擇了索引範圍掃描                    
  82.                                                                                                                                   
  83. 67 rows selected.                                                                                                                 
  84.                                                                                                                                   
  85. Execution Plan                                                                                                                    
  86. ----------------------------------------------------------           
  87. Plan hash value: 603312277                                                                                                        
  88.                                                                                                                                   
  89. ---------------------------------------------------------------------------------------------                
  90. | Id  | Operation                   | Name          | Rows  | Bytes | Cost (%CPU)| Time     |             
  91. ---------------------------------------------------------------------------------------------             
  92. |   0 | SELECT STATEMENT            |               |    68 |  4624 |     3   (0)| 00:00:01 |           
  93. |   1 |  TABLE ACCESS BY INDEX ROWID| EMPLOYEES     |    68 |  4624 |     3   (0)| 00:00:01 |               
  94. |*  2 |   INDEX RANGE SCAN          | EMP_EMP_ID_PK |    68 |       |     1   (0)| 00:00:01 | -->索引範圍掃描方式     
  95. ---------------------------------------------------------------------------------------------                            
  96. Predicate Information (identified by operation id):                                                
  97. ---------------------------------------------------                                 
  98.     2 - access("EMPLOYEE_ID">=140)                                        
  99.                                                                                                                                   
  100. 3) 用UNION 替換OR(適用於索引列)                                                                                                   
  101.     通常情況下,使用UNION 替換WHERE子句中的OR將會起到較好的效果.基於索引列使用OR使得優化器傾向於使用全表掃描,而不是掃描索引.      
  102.     注意,以上規則僅適用於多個索引列有效。 如果有column沒有被索引, 查詢效率可能會因爲你沒有選擇OR而降低。                         
  103. -->低效:                              
  104. SELECT deptno, dname                                                                                                              
  105. FROM   dept                                                                                                                       
  106. WHERE  loc = 'DALLAS' OR deptno = 20;                                                                                             
  107.                                                                                                                                   
  108. -->高效:                                        
  109. SELECT deptno, dname                                                                                                              
  110. FROM   dept                                                                                                                       
  111. WHERE  loc = 'DALLAS'                                                                                                             
  112. UNION                                                                                                                             
  113. SELECT deptno, dname                                                                                                              
  114. FROM   dept                                                                                                                       
  115. WHERE  deptno = 30                                                                                                                
  116.                                                                                                                                   
  117. -->經測試,由於數據量較少,此時where子句中的謂詞上都存在索引列時,兩者性能相當.                                                     
  118. -->假定where子句中存在兩列       
  119. scott@CNMMBO> create table t6 as select object_id,owner,object_name from dba_objects where owner='SYS' and rownum<1001;           
  120.                                                                                                                                   
  121. scott@CNMMBO> insert into t6 select object_id,owner,object_name from dba_objects where owner='SCOTT' and rownum<6;                
  122.                                                                                                                                   
  123. scott@CNMMBO> create index i_t6_object_id on t6(object_id);                                                  
  124.                                                                                                                                   
  125. scott@CNMMBO> create index i_t6_owner on t6(owner);                                                
  126.                                                                                                                                   
  127. scott@CNMMBO> insert into t6 select object_id,owner,object_name from dba_objects where owner='SYSTEM' and rownum<=300;            
  128.                                                                                                                                   
  129. scott@CNMMBO> commit;                                                                                                             
  130.                                                                                                                                   
  131. scott@CNMMBO> exec dbms_stats.gather_table_stats('SCOTT','T6',cascade=>true);    
  132.                                                                                                                                   
  133. scott@CNMMBO> select owner,count(*) from t6 group by owner;        
  134.                                                               
  135. OWNER                  COUNT(*)                                                  
  136. -------------------- ----------                                                     
  137. SCOTT                         5                                                         
  138. SYSTEM                      300                                                           
  139. SYS                        1000                                                          
  140.                                                                                                                                   
  141. scott@CNMMBO> select * from t6 where owner='SCOTT' and rownum<2;                                                                  
  142.                                                                             
  143.  OBJECT_ID OWNER                OBJECT_NAME                                                 
  144. ---------- -------------------- --------------------                                             
  145.      69450 SCOTT                T_TEST                                                         
  146.                                                                                 
  147. scott@CNMMBO> select * from t6 where object_id=69450 or owner='SYSTEM';                                                           
  148.                                                                                                                                   
  149. 301 rows selected.                                                                                                                
  150.                                                                                                                                   
  151. Execution Plan                                                                                                                    
  152. ----------------------------------------------------------        
  153. Plan hash value: 238853296                                                                                                       
  154. -----------------------------------------------------------------------------------------------             
  155. | Id  | Operation                    | Name           | Rows  | Bytes | Cost (%CPU)| Time     |           
  156. -----------------------------------------------------------------------------------------------         
  157. |   0 | SELECT STATEMENT             |                |   300 |  7200 |     5   (0)| 00:00:01 |         
  158. |   1 |  CONCATENATION               |                |       |       |            |          |      
  159. |   2 |   TABLE ACCESS BY INDEX ROWID| T6             |     1 |    24 |     2   (0)| 00:00:01 |              
  160. |*  3 |    INDEX RANGE SCAN          | I_T6_OBJECT_ID |     1 |       |     1   (0)| 00:00:01 |             
  161. |*  4 |   TABLE ACCESS BY INDEX ROWID| T6             |   299 |  7176 |     3   (0)| 00:00:01 |             
  162. |*  5 |    INDEX RANGE SCAN          | I_T6_OWNER     |   300 |       |     1   (0)| 00:00:01 |                
  163. -----------------------------------------------------------------------------------------------                 
  164.                                                                                                                                   
  165. Predicate Information (identified by operation id):                                                                               
  166. ---------------------------------------------------                                  
  167.    3 - access("OBJECT_ID"=69450)                    
  168.    4 - filter(LNNVL("OBJECT_ID"=69450))             
  169.    5 - access("OWNER"='SYSTEM')                  
  170.                                                                                                                                   
  171. Statistics                                                                                                                        
  172. ----------------------------------------------------------     
  173.           0  recursive calls                                                                                                      
  174.           0  db block gets                                                                                                        
  175.          46  consistent gets                                                                                                      
  176.           0  physical reads                                                                                                       
  177.           0  redo size                                                                                                            
  178.       11383  bytes sent via SQL*Net to client                                                                                     
  179.         712  bytes received via SQL*Net from client                                                                               
  180.          22  SQL*Net roundtrips to/from client                                                                                    
  181.           0  sorts (memory)                                                                                                       
  182.           0  sorts (disk)                                                                                                         
  183.         301  rows processed                                                                                                       
  184.                                                                                                                                   
  185. scott@CNMMBO> select * from t6 where owner='SYSTEM' or object_id=69450;                                                           
  186.                                                                                                                                   
  187. 301 rows selected.                                                                                                                
  188.                                                                                                                                   
  189. Execution Plan                                                                                                                    
  190. ----------------------------------------------------------     
  191. Plan hash value: 238853296                                                                                                        
  192. -----------------------------------------------------------------------------------------------               
  193. | Id  | Operation                    | Name           | Rows  | Bytes | Cost (%CPU)| Time     |               
  194. -----------------------------------------------------------------------------------------------             
  195. |   0 | SELECT STATEMENT             |                |   300 |  7200 |     5   (0)| 00:00:01 |              
  196. |   1 |  CONCATENATION               |                |       |       |            |          |                
  197. |   2 |   TABLE ACCESS BY INDEX ROWID| T6             |     1 |    24 |     2   (0)| 00:00:01 |                
  198. |*  3 |    INDEX RANGE SCAN          | I_T6_OBJECT_ID |     1 |       |     1   (0)| 00:00:01 |                  
  199. |*  4 |   TABLE ACCESS BY INDEX ROWID| T6             |   299 |  7176 |     3   (0)| 00:00:01 |          
  200. |*  5 |    INDEX RANGE SCAN          | I_T6_OWNER     |   300 |       |     1   (0)| 00:00:01 |          
  201. -----------------------------------------------------------------------------------------------               
  202.                                                                                                                                   
  203. Predicate Information (identified by operation id):                                                                               
  204. ---------------------------------------------------       
  205.    3 - access("OBJECT_ID"=69450)                                       
  206.    4 - filter(LNNVL("OBJECT_ID"=69450))                                   
  207.    5 - access("OWNER"='SYSTEM')                                     
  208.                                                                                                                                   
  209. Statistics                                                              
  210. ----------------------------------------------------------                    
  211.           1  recursive calls                                                                                                      
  212.           0  db block gets                                                                                                        
  213.          46  consistent gets                                                                                                      
  214.           0  physical reads                                                                                                       
  215.           0  redo size                                                                                                            
  216.       11383  bytes sent via SQL*Net to client                                                                                     
  217.         712  bytes received via SQL*Net from client                                                                               
  218.          22  SQL*Net roundtrips to/from client                                                                                    
  219.           0  sorts (memory)                                                                                                       
  220.           0  sorts (disk)                                                                                                         
  221.         301  rows processed                                                                                                       
  222.                                                                                                                                   
  223. scott@CNMMBO> select * from t6                                                                                                    
  224.   2  where object_id=69450                                                                                                        
  225.   3  union                                                                                                                        
  226.   4  select * from t6                                                                                                             
  227.   5  where owner='SYSTEM';                                                                                                        
  228.                                                                                                                                   
  229. 301 rows selected.                                                                                                                
  230.                                                                                                                                   
  231. Execution Plan                                                                                                                    
  232. ----------------------------------------------------------     
  233. Plan hash value: 370530636                                                                                                        
  234. ------------------------------------------------------------------------------------------------            
  235. | Id  | Operation                     | Name           | Rows  | Bytes | Cost (%CPU)| Time     |           
  236. ------------------------------------------------------------------------------------------------           
  237. |   0 | SELECT STATEMENT              |                |   301 |  7224 |     7  (72)| 00:00:01 |           
  238. |   1 |  SORT UNIQUE                  |                |   301 |  7224 |     7  (72)| 00:00:01 |         
  239. |   2 |   UNION-ALL                   |                |       |       |            |          |         
  240. |   3 |    TABLE ACCESS BY INDEX ROWID| T6             |     1 |    24 |     2   (0)| 00:00:01 |          
  241. |*  4 |     INDEX RANGE SCAN          | I_T6_OBJECT_ID |     1 |       |     1   (0)| 00:00:01 |      
  242. |   5 |    TABLE ACCESS BY INDEX ROWID| T6             |   300 |  7200 |     3   (0)| 00:00:01 |         
  243. |*  6 |     INDEX RANGE SCAN          | I_T6_OWNER     |   300 |       |     1   (0)| 00:00:01 |      
  244. ------------------------------------------------------------------------------------------------                 
  245.                                                                                                                                   
  246. Predicate Information (identified by operation id):                                                                               
  247. ---------------------------------------------------                                                                                
  248.    4 - access("OBJECT_ID"=69450)                         
  249.    6 - access("OWNER"='SYSTEM')                                      
  250.                                                                                                                                   
  251. Statistics                                                                                                                        
  252. ----------------------------------------------------------                                                                         
  253.           1  recursive calls                                                                                                      
  254.           0  db block gets                                                                                                        
  255.           7  consistent gets                                                                                                      
  256.           0  physical reads                                                                                                       
  257.           0  redo size                                                                                                            
  258.       11383  bytes sent via SQL*Net to client                                                                                     
  259.         712  bytes received via SQL*Net from client                                                                               
  260.          22  SQL*Net roundtrips to/from client                                                                                    
  261.           1  sorts (memory)                                                                                                       
  262.           0  sorts (disk)                                                                                                         
  263.         301  rows processed                                                                                                       
  264.                                                                                                                                   
  265. -->從上面的統計信息可知,consistent gets由46下降爲7,故當where子句中謂詞上存在索引時,使用union替換or更高效                        
  266. -->即使當列object_id與owner上不存在索引時,使用union仍然比or更高效(在Oracle 10g R2與Oracle 11g R2測試)                             
  267.                                                                                                                                   
  268. 4) 避免索引列上使用函數                                                                                                           
  269. -->下面是一個來自實際生產環境的例子                                                                                                
  270. -->表acc_pos_int_tbl上business_date列存在索引,由於使用了SUBSTR函數,此時索引失效,使用全表掃描                                     
  271. SELECT acc_num                                                                                                                    
  272.      , curr_cd                                                                                                                    
  273.      , DECODE( '20110728'                                                
  274.              , ( SELECT TO_CHAR( LAST_DAY( TO_DATE( '20110728''YYYYMMDD' ) ), 'YYYYMMDD' ) FROM dual ), 0       
  275.              ,   adj_credit_int_lv1_amt                      
  276.                + adj_credit_int_lv2_amt                            
  277.                - adj_debit_int_lv1_amt                               
  278.                - adj_debit_int_lv2_amt )                                  
  279.           AS interest                                               
  280. FROM   acc_pos_int_tbl                                          
  281. WHERE  SUBSTR( business_date, 1, 6 ) = SUBSTR( '20110728', 1, 6 ) AND business_date <= '20110728';       
  282.                                                                                                                                   
  283. -->改進的辦法              
  284. SELECT acc_num                                             
  285.      , curr_cd                                           
  286.      , DECODE( '20110728'                                  
  287.              , ( SELECT TO_CHAR( LAST_DAY( TO_DATE( '20110728''YYYYMMDD' ) ), 'YYYYMMDD' ) FROM dual ), 0    
  288.              ,   adj_credit_int_lv1_amt                      
  289.                + adj_credit_int_lv2_amt                         
  290.                - adj_debit_int_lv1_amt                            
  291.                - adj_debit_int_lv2_amt )                             
  292.           AS interest                                       
  293. FROM   acc_pos_int_tbl acc_pos_int_tbl                                               
  294. WHERE  business_date >= TO_CHAR( LAST_DAY( ADD_MONTHS( TO_DATE( '20110728''yyyymmdd' ), -1 ) )    
  295.                                 + 1, 'yyyymmdd' )                        
  296.        AND business_date <= '20110728';                   
  297.                                                                                                                                   
  298. -->下面的例子雖然沒有使用函數,但字符串連接同樣導致索引失效                                                                        
  299. -->低效:                       
  300. SELECT account_name, amount                                                                                                       
  301. FROM   transaction                                                                                                                
  302. WHERE  account_name                                                                                                               
  303.        || account_type = 'AMEXA';                                                                                                 
  304.                                                                                                                                   
  305. -->高效:                           
  306. SELECT account_name, amount                                                                                                       
  307. FROM   transaction                                                                                                                
  308. WHERE  account_name = 'AMEX' AND account_type = 'A';                                                                              
  309.                                                                                                                                   
  310. 5) 比較不匹配的數據類型                                                                                                           
  311. -->下面的查詢中business_date列上存在索引,且爲字符型,這種                                                                           
  312. -->低效:                                     
  313. SELECT *                                                                                                                          
  314. FROM   acc_pos_int_tbl                                                                                                            
  315. WHERE  business_date = 20090201;                                                                                                 
  316.                                                                                                                                   
  317. Execution Plan                                                                                                                    
  318. ----------------------------------------------------------         
  319. Plan hash value: 2335235465                      
  320.                                                  
  321. -------------------------------------------------------------------------------------                  
  322. | Id  | Operation         | Name            | Rows  | Bytes | Cost (%CPU)| Time     |                         
  323. -------------------------------------------------------------------------------------                         
  324. |   0 | SELECT STATEMENT  |                 | 37516 |  2857K|   106K  (1)| 00:21:17 |                      
  325. |*  1 |  TABLE ACCESS FULL| ACC_POS_INT_TBL | 37516 |  2857K|   106K  (1)| 00:21:17 |                 
  326. -------------------------------------------------------------------------------------                     
  327.                                                                                                                                   
  328. Predicate Information (identified by operation id):                          
  329. ---------------------------------------------------          
  330.      1 - filter(TO_NUMBER("BUSINESS_DATE")=20090201)    -->這裏可以看到產生了類型轉換              
  331.                                                                                                                                   
  332. -->高效:                                         
  333. SELECT *                                                                                                                          
  334. FROM   acc_pos_int_tbl                                                                                                            
  335. WHERE  business_date = '20090201'                                                                                                 
  336.                                                                                                                                   
  337. 6) 索引列上使用 NULL 值             
  338.     IS NULLIS NOT NULL會限制索引的使用,因爲數據中沒有值等於NULL值,即便是NULL值也不等於NULL值.且NULL值不存儲在於索引之中    
  339. 因此應儘可能避免在索引類上使用NULL值                                                                                              
  340.                                                                                                                                   
  341. SELECT acc_num                                                                                                                    
  342.      , pl_cd                                                                                                                      
  343.      , order_qty                                                                                                                  
  344.      , trade_date                                                                                                                 
  345. FROM   trade_client_tbl                                                                                                           
  346. WHERE  input_date IS NOT NULL;                                                                                                    
  347.                                                                                                                                   
  348. Execution Plan                                              
  349. ----------------------------------------------------------                           
  350. Plan hash value: 901462645                                        
  351. --------------------------------------------------------------------------------------                    
  352. | Id  | Operation         | Name             | Rows  | Bytes | Cost (%CPU)| Time     |                     
  353. --------------------------------------------------------------------------------------                    
  354. |   0 | SELECT STATEMENT  |                  |     1 |    44 |    15   (0)| 00:00:01 |                
  355. |*  1 |  TABLE ACCESS FULL| TRADE_CLIENT_TBL |     1 |    44 |    15   (0)| 00:00:01 |                     
  356. --------------------------------------------------------------------------------------                    
  357.                                             
  358. alter table trade_client_tbl modify (input_date not null);            
  359.                                                                      
  360. 不推薦使用的查詢方式                                        
  361. SELECT * FROM table_name WHERE col IS NOT NULL                        
  362.                                                                   
  363. SELECT * FROM table_name WHERE col IS NULL                                 
  364.                                                                                                                                   
  365. 推薦使用的方式                        
  366. SELECT * FROM table_name WHERE col >= 0 --儘可能的使用 =, >=, <=, like 等運算符        
  367. -->Author: Robinson Cheng                 
  368. -->Blog: http://blog.csdn.net/robinson_0612                 

三、總結
1、儘可能最小化基表數據以及中間結果集(通過過濾條件避免後續產生不必要的計算與聚合)
2、爲where子句中的謂詞信息提供最佳的訪問路徑(rowid訪問,索引訪問)
3、使用合理的SQL寫法來避免過多的Oracle內部開銷以提高性能
4、合理的使用提示以提高表之間的連接來提高連接效率(如避免迪卡爾集,將不合理的嵌套連接改爲hash連接等)

 

四、更多參考

Oracle SQL tuning 步驟

啓用用戶進程跟蹤

父遊標、子游標及共享遊標

綁定變量及其優缺點

dbms_xplan之display_cursor函數的使用

dbms_xplan之display函數的使用

執行計劃中各字段各模塊描述

使用 EXPLAIN PLAN 獲取SQL語句執行計劃

啓用 AUTOTRACE 功能

函數使得索引列失效

Oracle 綁定變量窺探

PL/SQL 聯合數組與嵌套表

發佈了35 篇原創文章 · 獲贊 2 · 訪問量 10萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章