Select 語句執行順序以及如何提高Oracle 基本查詢效率

原文地址:http://www.cnblogs.com/likeju/p/5039128.html


 首先,要了解在Oracle中Sql語句運行的機制。以下是sql語句的執行步驟:
              1)語法分析,分析語句的語法是否符合規範,衡量語句中各表達式的意義。
              2)語義分析,檢查語句中涉及的所有數據庫對象是否存在,且用戶有相應的權限。
              3)視圖轉換,將涉及視圖的查詢語句轉換爲相應的對基表查詢語句。
              4)表達式轉換, 將複雜的 SQL 表達式轉換爲較簡單的等效連接表達式。
              5)選擇優化器,不同的優化器一般產生不同的“執行計劃”
              6)選擇連接方式, ORACLE 有三種連接方式,對多表連接 ORACLE 可選擇適當的連接方式。
              7)選擇連接順序, 對多表連接 ORACLE 選擇哪一對錶先連接,選擇這兩表中哪個表做爲源數據表。
              8)選擇數據的搜索路徑,根據以上條件選擇合適的數據搜索路徑,如是選用全表搜索還是利用索引或是其他的方式。
              9)運行“執行計劃”。

       這裏不得不提的是Oracle共享原理:將執行過的SQL語句存放在內存的共享池(shared buffer pool)中,可以被所有的數據庫用戶共享當你執行一個SQL語句(有時被稱爲一個遊標)時,如果它和之前的執行過的語句完全相同, Oracle就能很快獲得已經被解析的語句以及最好的 執行路徑. 這個功能大大地提高了SQL的執行性能並節省了內存的使用。

       在瞭解了SQL語句的運行機制與Oracle共享原理後,我們可以知道SQL語句的書寫方式對SQL語句的執行效率有很大的影響。那麼下面我們瞭解一下SQL中Select語句中各個關鍵字執行的順序。

       SQL語言不同於其他編程語言的最明顯特徵是處理代碼的順序。在大多數據庫語言中,代碼按編碼順序被處理。但在SQL語句中,第一個被處理的子句是FROM,而不是第一齣現的SELECT。SQL查詢處理的步驟序號:
(8) SELECT (9) DISTINCT (11) <TOP_specification> <select_list> 
(1) FROM <left_table> 
(3) <join_type> JOIN <right_table> 
(2) ON <join_condition> 
(4) WHERE <where_condition> 
(5) GROUP BY <group_by_list> 
(6) WITH {CUBE | ROLLUP} 
(7) HAVING <having_condition> 
(10) ORDER BY <order_by_list> 
  以上每個步驟都會產生一個虛擬表,該虛擬表被用作下一個步驟的輸入。這些虛擬表對調用者(客戶端應用程序或者外部查詢)不可用。只有最後一步生成的表纔會會給調用者。如果沒有在查詢中指定某一個子句,將跳過相應的步驟。
  邏輯查詢處理階段簡介:
  1、 FROM:對FROM子句中的前兩個表執行笛卡爾積(交叉聯接),生成虛擬表VT1。表名執行順序是從後往前,所以數據較少的表儘量放後。
  2、 ON:對VT1應用ON篩選器,只有那些使爲真才被插入到TV2。
  3、 OUTER (JOIN):如果指定了OUTER JOIN(相對於CROSS JOIN或INNER JOIN),保留表中未找到匹配的行將作爲外部行添加到VT2,生成TV3。如果FROM子句包含兩個以上的表,則對上一個聯接生成的結果表和下一個表重複執行步驟1到步驟3,直到處理完所有的表位置。
  4、 WHERE:對TV3應用WHERE篩選器,只有使爲true的行才插入TV4。執行順序爲從前往後或者說從左到右。
  5、 GROUP BY:按GROUP BY子句中的列列表對TV4中的行進行分組,生成TV5。執行順序從左往右分組。
  6、 CUTE|ROLLUP:把超組插入VT5,生成VT6。
  7、 HAVING:對VT6應用HAVING篩選器,只有使爲true的組插入到VT7。Having語句很耗資源,儘量少用      8、 SELECT:處理SELECT列表,產生VT8。
  9、 DISTINCT:將重複的行從VT8中刪除,產品VT9。
  10、ORDER BY:將VT9中的行按ORDER BY子句中的列列表順序,生成一個遊標(VC10)。執行順序從左到右,是一個很耗資源的語句。
       11、TOP:從VC10的開始處選擇指定數量或比例的行,生成表TV11,並返回給調用者。

       

 

        看到這裏,應該是清楚了整個SQL語句整個執行的過程,那麼我們就接下來進一步要坐得就是在實現功能同時有考慮性能的思想,努力提高SQL的執行效率。

            第一、只返回需要的數據
        返回數據到客戶端至少需要數據庫提取數據、網絡傳輸數據、客戶端接收數據以及客戶端處理數據等環節,如果返回不需要的數據,就會增加服務器、網絡和客戶端的無效勞動,其害處是顯而易見的,避免這類事件需要注意:  

        A、橫向來看,
            (1)不要寫SELECT *的語句,而是選擇你需要的字段。
            (2)當在SQL語句中連接多個表時, 請使用表的別名並把別名前綴於每個Column上.這樣一來,就可以減少解析的時間並減少那些由Column歧義引起的語法錯誤。
        B、縱向來看,
            (1)合理寫WHERE子句,不要寫沒有WHERE的SQL語句。
            (2) SELECT TOP N * --沒有WHERE條件的用此替代

       第二、儘量少做重複的工作

            A、控制同一語句的多次執行,特別是一些基礎數據的多次執行是很多程序員很少注意的。
            B、減少多次的數據轉換,也許需要數據轉換是設計的問題,但是減少次數是程序員可以做到的。
            C、杜絕不必要的子查詢和連接表,子查詢在執行計劃一般解釋成外連接,多餘的連接錶帶來額外的開銷。
            D、合併對同一表同一條件的多次UPDATE。
            E、UPDATE操作不要拆成DELETE操作+INSERT操作的形式,雖然功能相同,但是性能差別是很大的。

        第三、注意臨時表和表變量的用法

        在複雜系統中,臨時表和表變量很難避免,關於臨時表和表變量的用法,需要注意:
             A、如果語句很複雜,連接太多,可以考慮用臨時表和表變量分步完成。

             B、如果需要多次用到一個大表的同一部分數據,考慮用臨時表和表變量暫存這部分數據。
             C、如果需要綜合多個表的數據,形成一個結果,可以考慮用臨時表和表變量分步彙總這多個表的數據。
             D、其他情況下,應該控制臨時表和表變量的使用。
             E、關於臨時表和表變量的選擇,很多說法是表變量在內存,速度快,應該首選表變量,但是在實際使用中發現,(1)主要考慮需要放在臨時表的數據量,在數據量較多的情況下,臨時表的速度反而更快。(2)執行時間段與預計執行時間(多長)
             F、關於臨時表產生使用SELECT INTO和CREATE TABLE + INSERT INTO的選擇,一般情況下,SELECT INTO會比CREATE TABLE + INSERT INTO的方法快很多,但是SELECT INTO會鎖定TEMPDB的系統表SYSOBJECTS、SYSINDEXES、SYSCOLUMNS,在多用戶併發環境下,容易阻塞其他進程,所以我的建議是,在併發系統中,儘量使用CREATE TABLE + INSERT INTO,而大數據量的單個語句使用中,使用SELECT INTO。

        第四、注意子查詢的用法

        子查詢是一個 SELECT 查詢,它嵌套在 SELECT、INSERT、UPDATE、DELETE 語句或其它子查詢中。任何允許使用表達式的地方都可以使用子查詢,子查詢可以使我們的編程靈活多樣,可以用來實現一些特殊的功能。但是在性能上,往往一個不合適的子查詢用法會形成一個性能瓶頸。如果子查詢的條件中使用了其外層的表的字段,這種子查詢就叫作相關子查詢。
相關子查詢可以用IN、NOT IN、EXISTS、NOT EXISTS引入。 關於相關子查詢,應該注意:
(1)

A、NOT IN、NOT EXISTS的相關子查詢可以改用LEFT JOIN代替寫法。
比如:

SELECT PUB_NAME

FROM PUBLISHERS

WHERE PUB_ID NOT IN (SELECT PUB_ID FROM TITLES WHERE TYPE = 'BUSINESS')

可以改寫成:

SELECT A.PUB_NAME

FROM PUBLISHERS A LEFT JOIN TITLES B ON B.TYPE = 'BUSINESS' AND A.PUB_ID=B. PUB_ID

 WHERE B.PUB_ID IS NULL
(2)
SELECT TITLE

FROM TITLES

WHERE NOT EXISTS (SELECT TITLE_ID FROM SALES WHERE TITLE_ID = TITLES.TITLE_ID)

可以改寫成:
SELECT TITLE

FROM TITLES LEFT JOIN SALES ON SALES.TITLE_ID = TITLES.TITLE_ID

WHERE SALES.TITLE_ID IS NULL

B、 如果保證子查詢沒有重複 ,IN、EXISTS的相關子查詢可以用INNER JOIN 代替。

比如:
SELECT PUB_NAME

FROM PUBLISHERS

WHERE PUB_ID IN (SELECT PUB_ID FROM TITLES

WHERE TYPE = 'BUSINESS')

可以改寫成:
SELECT DISTINCT A.PUB_NAME

FROM PUBLISHERS A INNER JOIN TITLES B ON B.TYPE = 'BUSINESS' AND A.PUB_ID=B. PUB_ID

C、 IN的相關子查詢用EXISTS代替,比如
SELECT PUB_NAME

FROM PUBLISHERS

WHERE PUB_ID IN (SELECT PUB_ID FROM TITLES WHERE TYPE = 'BUSINESS')

可以用下面語句代替:
SELECT PUB_NAME

FROM PUBLISHERS

WHERE EXISTS (SELECT 1 FROM TITLES WHERE TYPE = 'BUSINESS' AND PUB_ID= PUBLISHERS.PUB_ID) D、不要用COUNT(*)的子查詢判斷是否存在記錄,最好用LEFT JOIN或者EXISTS,比如有人寫這樣的語句:
SELECT JOB_DESC

FROM JOBS

WHERE (SELECT COUNT(*) FROM EMPLOYEE WHERE JOB_ID=JOBS.JOB_ID)=0

應該改成:
SELECT JOBS.JOB_DESC

FROM JOBS LEFT JOIN EMPLOYEE ON EMPLOYEE.JOB_ID=JOBS.JOB_ID

WHERE EMPLOYEE.EMP_ID IS NULL

SELECT JOB_DESC FROM JOBS WHERE (SELECT COUNT(*) FROM EMPLOYEE WHERE JOB_ID=JOBS.JOB_ID)<>0

應該改成:
SELECT JOB_DESC FROM JOBS

WHERE EXISTS (SELECT 1 FROM EMPLOYEE WHERE JOB_ID=JOBS.JOB_ID)  

       第五、儘量使用索引,並注意對含索引列的運算

       建立索引後,並不是每個查詢都會使用索引,在使用索引的情況下,索引的使用效率也會有很大的差別。只要我們在查詢語句中沒有強制指定索引,索引的選擇和使用方法是SQLSERVER的優化器自動作的選擇,而它選擇的根據是查詢語句的條件以及相關表的統計信息,這就要求我們在寫SQL語句的時候儘量使得優化器可以使用索引。爲了使得優化器能高效使用索引,寫語句的時候應該注意:
A、不要對索引字段進行運算,而要想辦法做變換,比如
SELECT ID FROM T WHERE NUM/2=100
應改爲:
SELECT ID FROM T WHERE NUM=100*2
-------------------------------------------------------
SELECT ID FROM T WHERE NUM/2=NUM1
如果NUM有索引應改爲:
SELECT ID FROM T WHERE NUM=NUM1*2
如果NUM1有索引則不應該改。
--------------------------------------------------------------------
發現過這樣的語句:
SELECT 年,月,金額 FROM 結餘表 WHERE 100*年+月=2010*100+10
應該改爲:
SELECT 年,月,金額 FROM 結餘表 WHERE 年=2010 AND月=10
B、 不要對索引字段進行格式轉換
日期字段的例子:
WHERE CONVERT(VARCHAR(10), 日期字段,120)='2010-07-15'
應該改爲
WHERE日期字段〉='2010-07-15' AND 日期字段<'2010-07-16' IS NULL

轉換的例子:

WHERE ISNULL(字段,'')<>''應改爲:WHERE字段<>'
WHERE ISNULL(字段,'')=''不應修改
WHERE ISNULL(字段,'F') ='T'應改爲: WHERE字段='T'
WHERE ISNULL(字段,'F')<>'T'不應修改
C、 不要對索引字段使用函數
WHERE LEFT(NAME, 3)='ABC' 或者WHERE SUBSTRING(NAME,1, 3)='ABC'
應改爲: WHERE NAME LIKE 'ABC%'
日期查詢的例子:
WHERE DATEDIFF(DAY, 日期,'2010-06-30')=0
應改爲:WHERE 日期>='2010-06-30' AND 日期 <'2010-07-01'
WHERE DATEDIFF(DAY, 日期,'2010-06-30')>0
應改爲:WHERE 日期 <'2010-06-30'
WHERE DATEDIFF(DAY, 日期,'2010-06-30')>=0
應改爲:WHERE 日期 <'2010-07-01'
WHERE DATEDIFF(DAY, 日期,'2010-06-30')<0
應改爲:WHERE 日期>='2010-07-01'
WHERE DATEDIFF(DAY, 日期,'2010-06-30')<=0
應改爲:WHERE 日期>='2010-06-30'
D、不要對索引字段進行多字段連接
比如:
WHERE FAME+ '. '+LNAME='HAIWEI.YANG'
應改爲:
WHERE FNAME='HAIWEI' AND LNAME='YANG'
        第六、注意多表連接的連接條件的選擇與表示

        多表連接的連接條件對索引的選擇有着重要的意義,所以我們在寫連接條件條件的時候需要特別注意。
            A、多表連接的時候,連接條件必須寫全,寧可重複,不要缺漏。
            B、連接條件儘量使用聚集索引
            C、注意ON、WHERE和HAVING部分條件的區別: ON是最先執行, WHERE次之,HAVING最後,因爲ON是先把不符合條件的記錄過濾後才進行統計,它就可以減少中間運算要處理的數據,按理說應該速度是最快的,WHERE也應該比 HAVING快點的,因爲它過濾數據後才進行SUM,在兩個表聯接時才用ON的,所以在一個表的時候,就剩下WHERE跟HAVING比較了
       1、考慮聯接優先順序:
       2、INNER JOIN
       3、LEFT JOIN (注:RIGHT JOIN 用 LEFT JOIN 替代)
       4、CROSS JOIN
        其它注意和了解的地方有:
            A、在IN後面值的列表中,將出現最頻繁的值放在最前面,出現得最少的放在最後面,減少判斷的次數
            B、注意UNION和UNION ALL的區別。--允許重複數據用UNION ALL好
            C、注意使用DISTINCT,在沒有必要時不要用
            D、TRUNCATE TABLE 與 DELETE 區別
            E、減少訪問數據庫的次數
       還有就是我們寫存儲過程,如果比較長的話,最後用標記符標開,因爲這樣可讀性很好,即使語句寫的不怎麼樣但是語句工整。如:

           --startof 查詢在職人數

               sql語句

           --end of

       正式機器上我們一般不能隨便調試程序,但是很多時候程序在我們本機上沒問題,但是進正式系統就有問題,但是我們又不能隨便在正式機器上操作,那麼怎麼辦呢?我們可以用回滾來調試我們的存儲過程或者是sql語句,從而排錯。

        BEGIN TRAN

            UPDATE a SET 字段='  '

        ROLLBACK

       作業存儲過程可以加上下面這段,這樣檢查錯誤可以放在存儲過程,如果執行錯誤回滾操作,但是如果程序裏面已經有了事務回滾,那麼存儲過程就不要寫事務了,這樣會導致事務回滾嵌套降低執行效率,但是我們很多時候可以把檢查放在存儲過程裏,這樣有利於我們解讀這個存儲過程,和排錯。
        BEGIN TRANSACTION

        --事務回滾開始

              --檢查報錯

              IF ( @@ERROR > 0 )

                  BEGIN

                     --回滾操作

                     ROLLBACK TRANSACTION

                     RAISERROR('刪除工作報告錯誤', 16, 3)

                  RETURN

                  END

         --結束事務

         COMMIT TRANSACTION 

        第七、有效使用Decode函數

             使用Decode函數可以有效避免重複掃描相同數據或重複連接相同表。


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