透過分析SQL語句的執行計劃最佳化SQL(總結)

531968912發表於2016-01-27
透過分析SQL語句的執行計劃最佳化SQL(總結)

做DBA快7年了,中間感悟很多。在DBA的日常工作中,調整個別效能較差的SQL語句時一項富有挑戰性的工作。其中的關鍵在於如何得到SQL語句的執行計劃和如何從SQL語句的執行計劃中發現問題。總是想將日常經驗的點點滴滴總結一下,但是直到最近才下定決心,總共花了3個週末時間,才將其整理成冊,便於自己日常工作。不好意思獨享,所以將其貼出來。

修改日誌:

2006.02.20:
根據網友反饋已做部分修改,但pdf檔案沒有做修改,修改部分在“如何產生執行計劃”關於set autotraceonly的介紹部分

第一章、第2章 並不是很重要,是自己的一些想法,關於如何做一個穩定、高效的應用系統的一些想法。

第三章以後都是比較重要的。

附錄的內容也是比較重要的。我常用該部分的內容。

前言
本文件主要介紹與SQL調整有關的內容,內容涉及多個方面:SQL語句執行的過程、ORACLE最佳化器,表之間的關聯,如何得到SQL執行計劃,如何分析執行計劃等內容,從而由淺到深的方式瞭解SQL最佳化的過程,使大家逐步步入SQL調整之門,然後你將發現……。

該文件的不當之處,敬請指出,以便進一步改正。請將其發往我的信箱:xu_yu_jin2000@sina.com。

如果引用本文的內容,請著名出處!
















作者:徐玉金
MSN:sunny_xyj@hotmail.com
Email:
日期:2005.12.12
活躍於: SunnyXu







目錄


第1章 效能調整綜述
第2章 有效的應用設計
第3章 SQL語句處理的過程
第4章 ORACLE的最佳化器
第5章 ORACLE的執行計劃
訪問路徑(方法) -- access path
表之間的連線
如何產生執行計劃
如何分析執行計劃
如何幹預執行計劃 - - 使用hints提示
具體案例分析
第6章 其它注意事項
附錄

第1章 效能調整綜述

Oracle資料庫是高度可調的資料庫產品。本章描述調整的過程和那些人員應與Oracle伺服器的調整有關,以及與調整相關聯的作業系統硬體和軟體。本章包括以下方面:
l 誰來調整系統?
l 什麼時候調整?
l 建立有效調整的目標
l 在設計和開發時的調整
l 調整產品系統
l 監控產品系統

誰來調整系統:
為了有效地調整系統,若干類人員必須交換資訊並牽涉到系統調整中,例如:
l 應用設計人員必須傳達應用系統的設計,使得每個人都清楚應用中的資料流動.
l 應用開發人員必須傳達他們選擇的實現策略,使得語句調整的過程中能快速、容易地識別有問題的應用模組和可疑的SQL語句.
l 資料庫管理人員必須仔細地監控系統活動並提供它們的資料,使得異常的系統效能可被快速得識別和糾正.
l 硬體/軟體管理人員必須傳達系統的硬體、軟體配置並提供它們的資料,使得相關人員能有效地設計和管理系統。

簡而言之,與系統涉及的每個人都在調整過程中起某些作用,當上面提及的那些人員傳達了系統的特性並提供了它們的資料,調整就能相對的容易和更快一些。

不幸的是,事實上的結果是:資料庫管理員對調整負有全部或主要的責任。但是,資料庫管理員很少有合適的系統方面的資料,而且,在很多情況下,資料庫管理員往往是在實施階段才介入資料庫,這就給調整工作帶來許多負面的影響,因為在設計階段的缺陷是不能透過DBA的調整而得以解決,而設計階段的缺陷往往對資料庫效能造成極大的影響。
其實,在真正成熟的開發環境下,開發人員作為純程式碼編寫人員時,對效能的影響最小,此時大部分的工作應由應用設計人員完成,而且資料庫管理員往往在前期的需求管理階段就介入,為設計人員提供必要的技術支援。
調整並不是資料庫管理員的專利,相反大部分應該是設計人員和開發人員的工作,這就需要設計人員和開發人員具體必要的資料庫知識,這樣才能組成一個高效的團隊,然而事實上往往並非如此。

什麼時候作調整?
多數人認為當使用者感覺效能差時才進行調整,這對調整過程中使用某些最有效的調整策略來說往往是太遲了。此時,如果你不願意重新設計應用的話,你只能透過重新分配記憶體(調整SGA)和調整I/O的辦法或多或少地提高效能。Oracle提供了許多特性,這些特性只有應用到正確地設計的系統中時才能夠很大地提高效能。

應用設計人員需要在設計階段設定應用的效能期望值。然後在設計和開發期間,應用設計人員應考慮哪些Oracle 特性可以對系統有好處,並使用這些特性。
透過良好的系統設計,你就可以在應用的生命週期中消除效能調整的代價和挫折。圖1-1圖1-2說明在應用的生命週期中調整的相對代價和收益,正如你見到的,最有效的調整時間是在設計階段。在設計期間的調整能以最低的代價給你最大的收益。


圖1-1 在應用生命週期中調整的代價 



      圖1-2 在應用生命週期中調整的收益

當然,即使在設計很好的系統中,也可能有效能降低。但這些效能降低應該是可控的和可以預見的。

調整目標
不管你正在設計或維護系統,你應該建立專門的效能目標,它使你知道何時要作調整。如果你試圖胡亂地改動初始化引數或SQl 語句,你可能會浪費調整系統的時間,而且無什麼大的收益。調整你的系統的最有效方法如下:
l 當設計系統時考慮效能
l 調整作業系統的硬體和軟體
l 識別效能瓶頸
l 確定問題的原因
l 採取糾正的動作
當你設計系統時,制定專門的目標;例如,響應時間小於3秒。當應用不能滿足此目標時,識別造成變慢的瓶頸(例如,I/O競爭),確定原因,採取糾正動作。在開發期間,你應測試應用研究,確定在採取應用之前是否滿足設計的效能目標。

當你正在維護生產庫系統時,有多種快速有效的方法來識別效能瓶頸。
不管怎樣,調整通常是一系列開銷。一旦你已確定了瓶頸,你可能要犧牲一些其它方面的指標來達到所要的結果。例如,如果I/O有問題,你可能需要更多記憶體或磁碟。如果不可能買,你可能要限制系統的併發性,來獲取所需的效能。然而,如果你已經明確地定義了效能的目標,那用什麼來交換高效能的決策就變的很容易的,因為你已經確定了哪些方面是最重要的,如過我的目標為高效能,可能犧牲一些空間資源。

隨著應用的越來越龐大,硬體效能的提高,全面的調整應用逐漸變成代價高昂的行為,在這樣情況下,要取得最大的投入/效率之比,較好的辦法是調整應用的關鍵部分,使其達到比較高的效能,這樣從總體上來說,整個系統的效能也是比較高的。這也就是有名的20/80原則,調整應用的20%(關鍵部分),能解決80%的問題。

在設計和開發系統時作調整
良好設計的系統可以防止在應用生命週期中產生效能問題。系統設計人員和應用開發人員必須瞭解Oracle的查詢處理機制以便寫出高效的SQL語句。“第2章 有效的應用設計”討論了你的系統中各種可用的配置,以及每種配置更適合哪種型別的應用。“第5章 最佳化器”討論了Oracle的查詢最佳化器,以及如何寫語句以獲取最快的結果。

當設計你的系統時,使用下列最佳化效能的準則:
l 消除客戶機/伺服器應用中不必要的網路傳輸。-- 使用儲存過程。
l 使用適合你係統的相應Oracle伺服器選件(例如,並行查詢或分散式資料庫)。
l 除非你的應用有特殊的需要,否則使用預設的Oracle鎖。
l 利用資料庫記住應用模組,以便你能以每個模組為基礎來追蹤效能。
l 選擇你的資料塊的最佳大小。 -- 原則上來說大一些的效能較好。
l 分佈你的資料,使得一個節點使用的資料本地存貯在該節點中。

調整產品系統
本節描述對應用系統快速、容易地找出效能瓶頸,並決定糾正動作的方法。這種方法依賴於對Oracle伺服器體系結構和特性的瞭解程度。在試圖調整你的系統前,你應熟悉Oracle調整的內容。

為調整你已有的系統,遵從下列步驟:
l 調整作業系統的硬體和軟體
l 透過查詢V $SESSION_WAIT檢視,識別效能的瓶頸,這個動態效能檢視列出了造成會話(session)等待的事件。
l 透過分析V $SESSION_WAIT中的資料,決定瓶頸的原因。
l 糾正存在的問題。

監控應用系統
這主要是透過監控oracle的動態檢視來完成。
各種有用的動態檢視:如v$session_wait, v$session_event等。


第2章 有效的應用設計
我們通常將最常用的應用分為2種型別:聯機事務處理型別(OLTP),決策支援系統(DSS)。

聯機事務處理(OLTP)
該型別的應用是高吞吐量,插入、更新、刪除操作比較多的系統,這些系統以不斷增長的大容量資料為特徵,它們提供給成百使用者同時存取,典型的OLTP系統是訂票系統,銀行的業務系統,訂單系統。OTLP的主要目標是可用性、速度、併發性和可恢復性。
當設計這類系統時,必須確保大量的併發使用者不能干擾系統的效能。還需要避免使用過量的索引與cluster 表,因為這些結構會使插入和更新操作變慢。

決策支援(DSS)
該型別的應用將大量資訊進行提取形成報告,協助決策者作出正確的判斷。典型的情況是:決策支援系統將OLTP應用收集的大量資料進行查詢。典型的應用為客戶行為分析系統(超市,保險等)。
決策支援的關鍵目標是速度、精確性和可用性。
該種型別的設計往往與OLTP設計的理念背道而馳,一般建議使用資料冗餘、大量索引、cluster table、並行查詢等。
近年來,該型別的應用逐漸與OLAP、資料倉儲緊密的聯絡在一起,形成的一個新的應用方向。

第3章 SQL語句處理的過程

在調整之前我們需要了解一些背景知識,只有知道這些背景知識,我們才能更好的去調整sql語句。
本節介紹了SQL語句處理的基本過程,主要包括:
· 查詢語句處理
· DML語句處理(insert, update, delete)
· DDL 語句處理(create .. , drop .. , alter .. , )
· 事務控制(commit, rollback)

SQL 語句的執行過程(SQL Statement Execution)
圖3-1 概要的列出了處理和執行一個sql語句的需要各個重要階段。在某些情況下,Oracle執行sql的過程可能與下面列出的各個階段的順序有所不同。如DEFINE階段可能在FETCH階段之前,這主要依賴你如何書寫程式碼。

對許多oracle的工具來說,其中某些階段會自動執行。絕大多數使用者不需要關心各個階段的細節問題,然而,知道執行的各個階段還是有必要的,這會幫助你寫出更高效的SQL語句來,而且還可以讓你猜測出效能差的SQL語句主要是由於哪一個階段造成的,然後我們針對這個具體的階段,找出解決的辦法。



圖 3-1 SQL語句處理的各個階段

DML語句的處理
本節給出一個例子來說明在DML語句處理的各個階段到底發生了什麼事情。
假設你使用Pro*C程式來為指定部門的所有職員增加工資。程式已經連到正確的使用者,你可以在你的程式中嵌入如下的SQL語句:
EXEC SQL UPDATE employees
SET salary = 1.10 * salary
WHERE department_id = :var_department_id;
var_department_id是程式變數,裡面包含部門號,我們要修改該部門的職員的工資。當這個SQL語句執行時,使用該變數的值。
每種型別的語句都需要如下階段:
· 第1步: Create a Cursor 建立遊標
· 第2步: Parse the Statement 分析語句
· 第5步: Bind Any Variables 繫結變數
· 第7步: Run the Statement 執行語句
· 第9步: Close the Cursor 關閉遊標
如果使用了並行功能,還會包含下面這個階段:
· 第6步: Parallelize the Statement 並行執行語句
如果是查詢語句,則需要以下幾個額外的步驟,如圖 3所示:
· 第3步: Describe Results of a Query 描述查詢的結果集
· 第4步: Define Output of a Query 定義查詢的輸出資料
· 第8步: Fetch Rows of a Query 取查詢出來的行

下面具體說一下每一步中都發生了什麼事情:.
第1步: 建立遊標(Create a Cursor)
由程式介面呼叫建立一個遊標(cursor)。任何SQL語句都會建立它,特別在執行DML語句時,都是自動建立遊標的,不需要開發人員干預。多數應用中,遊標的建立是自動的。然而,在預編譯程式(pro*c)中游標的建立,可能是隱含的,也可能顯式的建立。在儲存過程中也是這樣的。

第2步:分析語句(Parse the Statement)
在語法分析期間,SQL語句從使用者程式傳送到Oracle,SQL語句經語法分析後,SQL語句本身與分析的資訊都被裝入到共享SQL區。在該階段中,可以解決許多型別的錯誤。

語法分析分別執行下列操作:
l 翻譯SQL語句,驗證它是合法的語句,即書寫正確
l 實現資料字典的查詢,以驗證是否符合表和列的定義
l 在所要求的物件上獲取語法分析鎖,使得在語句的語法分析過程中不改變這些物件的定義
l 驗證為存取所涉及的模式物件所需的許可權是否滿足
l 決定此語句最佳的執行計劃
l 將它裝入共享SQL區
l 對分佈的語句來說,把語句的全部或部分路由到包含所涉及資料的遠端節點
以上任何一步出現錯誤,都將導致語句報錯,中止執行。

只有在共享池中不存在等價SQL語句的情況下,才對SQL語句作語法分析。在這種情況下,資料庫核心重新為該語句分配新的共享SQL區,並對語句進行語法分析。進行語法分析需要耗費較多的資源,所以要儘量避免進行語法分析,這是最佳化的技巧之一。

語法分析階段包含了不管此語句將執行多少次,而只需分析一次的處理要求。Oracle只對每個SQL語句翻譯一次,在以後再次執行該語句時,只要該語句還在共享SQL區中,就可以避免對該語句重新進行語法分析,也就是此時可以直接使用其對應的執行計劃對資料進行存取。這主要是透過繫結變數(bind variable)實現的,也就是我們常說的共享SQL,後面會給出共享SQL的概念。

雖然語法分析驗證了SQL語句的正確性,但語法分析只能識別在SQL語句執行之前所能發現的錯誤(如書寫錯誤、許可權不足等)。因此,有些錯誤透過語法分析是抓不到的。例如,在資料轉換中的錯誤或在資料中的錯(如企圖在主鍵中插入重複的值)以及死鎖等均是隻有在語句執行階段期間才能遇到和報告的錯誤或情況。

查詢語句的處理
查詢與其它型別的SQL語句不同,因為在成功執行後作為結果將返回資料。其它語句只是簡單地返回成功或失敗,而查詢則能返回一行或許多行資料。查詢的結果均採用表格形式,結果行被一次一行或者批次地被檢索出來。從這裡我們可以得知批次的fetch資料可以降低網路開銷,所以批次的fetch也是最佳化的技巧之一。

有些問題只與查詢處理相關,查詢不僅僅指SELECT語句,同樣也包括在其它SQL語句中的隱含查詢。例如,下面的每個語句都需要把查詢作為它執行的一部分:
INSERT INTO table SELECT...
UPDATE table SET x = y WHERE...
DELETE FROM table WHERE...
CREATE table AS SELECT...
具體來說,查詢
· 要求讀一致性
· 可能使用回滾段作中間處理
· 可能要求SQL語句處理描述、定義和取資料階段

第3步: 描述查詢結果(Describe Results of a Query)
描述階段只有在查詢結果的各個列是未知時才需要;例如,當查詢由使用者互動地輸入需要輸出的列名。在這種情況要用描述階段來決定查詢結果的特徵(資料型別,長度和名字)。

[/B]第4步: 定義查詢的輸出資料(Define Output of a Query) [/B]
在查詢的定義階段,你指定與查詢出的列值對應的接收變數的位置、大小和資料型別,這樣我們透過接收變數就可以得到查詢結果。如果必要的話,Oracle會自動實現資料型別的轉換。這是將接收變數的型別與對應的列型別相比較決定的。

第5步: 繫結變數(Bind Any Variables)
此時,Oracle知道了SQL語句的意思,但仍沒有足夠的資訊用於執行該語句。Oracle 需要得到在語句中列出的所有變數的值。在該例中,Oracle需要得到對department_id列進行限定的值。得到這個值的過程就叫繫結變數(binding variables)

此過程稱之為將變數值捆綁進來。程式必須指出可以找到該數值的變數名(該變數被稱為捆綁變數,變數名實質上是一個記憶體地址,相當於指標)。應用的終端使用者可能並沒有發覺他們正在指定捆綁變數,因為Oracle 的程式可能只是簡單地指示他們輸入新的值,其實這一切都在程式中自動做了。

因為你指定了變數名,在你再次執行之前無須重新捆綁變數。你可以改變繫結變數的值,而Oracle在每次執行時,僅僅使用記憶體地址來查詢此值。

如果Oracle 需要實現自動資料型別轉換的話(除非它們是隱含的或預設的),你還必須對每個值指定資料型別和長度。關於這些資訊可以參考oracle的相關文件,如Oracle Call Interface Programmer's Guide

第6步: 並行執行語句(Parallelize the Statement )
ORACLE 可以在SELECTs, INSERTs, UPDATEs, MERGEs, DELETEs語句中執行相應並行查詢操作,對於某些DDL操作,如建立索引、用子查詢建立表、在分割槽表上的操作,也可以執行並行操作。並行化可以導致多個伺服器程式(oracle server processes)為同一個SQL語句工作,使該SQL語句可以快速完成,但是會耗費更多的資源,所以除非很有必要,否則不要使用並行查詢。

第7步: 執行語句(Run the Statement)
到了現在這個時候,Oracle擁有所有需要的資訊與資源,因此可以真正執行SQL語句了。如果該語句為SELECT查詢或INSERT語句,則不需要鎖定任何行,因為沒有資料需要被改變。然而,如果語句為UPDATE或DELETE語句,則該語句影響的所有行都被鎖定,防止該使用者提交或回滾之前,別的使用者對這些資料進行修改。這保證了資料的一致性。

對於某些語句,你可以指定執行的次數,這稱為批處理(array processing)。指定執行N次,則繫結變數與定義變數被定義為大小為N的陣列的開始位置,這種方法可以減少網路開銷,也是最佳化的技巧之一。

第8步: 取出查詢的行(Fetch Rows of a Query)
在fetch階段,行資料被取出來,每個後續的存取操作檢索結果集中的下一行資料,直到最後一行被取出來。上面提到過,批次的fetch是最佳化的技巧之一。

第9步: 關閉遊標(Close the Cursor)
SQL語句處理的最後一個階段就是關閉遊標

DDL語句的處理(DDL Statement Processing)
DDL語句的執行不同與DML語句和查詢語句的執行,這是因為DDL語句執行成功後需要對資料字典資料進行修改。對於DDL語句,語句的分析階段實際上包括分析、查詢資料字典資訊和執行。

事務管理語句、會話管理語句、系統管理語句只有分析與執行階段,為了重新執行該語句,會重新分析與執行該語句。

事務控制(Control of Transactions)
一般來說,只有使用ORACLE程式設計介面的應用設計人員才關心操作的型別,並把相關的操作組織在一起,形成一個事務。一般來說,我門必須定義事務,這樣在一個邏輯單元中的所有工作可以同時被提交或回滾,保證了資料的一致性。一個事務應該由邏輯單元中的所有必須部分組成,不應該多一個,也不應該少一個。
· 在事務開始和結束的這段時間內,所有被引用表中的資料都應該在一致的狀態(或可以被回溯到一致的狀態)
· 事務應該只包含可以對資料進行一致更改(one consistent change to the data)的SQL語句

例如,在兩個帳號之間的轉帳(這是一個事務或邏輯工作單元),應該包含從一個帳號中借錢(由一個SQL完成),然後將借的錢存入另一個帳號(由另一個SQL完成)。這2個操作作為一個邏輯單元,應該同時成功或同時失敗。其它不相關的操作,如向一個帳戶中存錢,不應該包含在這個轉帳事務中。

在設計應用時,除了需要決定哪種型別的操作組成一個事務外,還需要決定使用BEGIN_DISCRETE_TRANSACTIO儲存過程是否對提高小的、非分散式的事務的效能有作用。

第4章 ORACLE的最佳化器

最佳化器有時也被稱為查詢最佳化器,這是因為查詢是影響資料庫效能最主要的部分,不要以為只有SELECT語句是查詢。實際上,帶有任何WHERE條件的DML(INSERT、UPDATE、DELETE)語句中都包含查詢要求,在後面的文章中,當說到查詢時,不一定只是指SELECT語句,也有可能指DML語句中的查詢部分。最佳化器是所有關聯式資料庫引擎中的最神秘、最富挑戰性的部件之一,從效能的角度看也是最重要的部分,它效能的高低直接關係到資料庫效能的好壞。

我們知道,SQL語句同其它語言(如C語言)的語句不一樣,它是非過程化(non-procedural)的語句,即當你要取資料時,不需要告訴資料庫透過何種途徑去取資料,如到底是透過索引取資料,還是應該將表中的每行資料都取出來,然後再透過一一比較的方式取資料(即全表掃描),這是由資料庫的最佳化器決定的,這就是非過程化的含義,也就是說,如何取資料是由最佳化器決定,而不是應用開發者透過程式設計決定。在處理SQL的SELECT、UPDATE、INSERT或DELETE語句時,Oracle 必須訪問語句所涉及的資料,Oracle的最佳化器部分用來決定訪問資料的有效路徑,使得語句執行所需的I/O和處理時間最小。

為了實現一個查詢,核心必須為每個查詢定製一個查詢策略,或為取出符合條件的資料生成一個執行計劃(execution plan)。典型的,對於同一個查詢,可能有幾個執行計劃都符合要求,都能得到符合條件的資料。例如,參與連線的表可以有多種不同的連線方法,這取決於連線條件和最佳化器採用的連線方法。為了在多個執行計劃中選擇最優的執行計劃,最佳化器必須使用一些實際的指標來衡量每個執行計劃使用的資源(I/0次數、CPU等),這些資源也就是我們所說的代價(cost)。如果一個執行計劃使用的資源多,我們就說使用執行計劃的代價大。以執行計劃的代價大小作為衡量標準,最佳化器選擇代價最小的執行計劃作為真正執行該查詢的執行計劃,並拋棄其它的執行計劃。

在ORACLE的發展過程中,一共開發過2種型別的最佳化器:基於規則的最佳化器和基於代價的最佳化器。這2種最佳化器的不同之處關鍵在於:取得代價的方法與衡量代價的大小不同。現對每種最佳化器做一下簡單的介紹:

基於規則的最佳化器 -- Rule Based (Heuristic) Optimization(簡稱RBO):
在ORACLE7之前,主要是使用基於規則的最佳化器。ORACLE在基於規則的最佳化器中採用啟發式的方法(Heuristic Approach)或規則(Rules)來生成執行計劃。例如,如果一個查詢的where條件(where clause)包含一個謂詞(predicate,其實就是一個判斷條件,如”=”, “>”, ”<”等),而且該謂詞上引用的列上有有效索引,那麼最佳化器將使用索引訪問這個表,而不考慮其它因素,如表中資料的多少、表中資料的易變性、索引的可選擇性等。此時資料庫中沒有關於表與索引資料的統計性描述,如表中有多上行,每行的可選擇性等。最佳化器也不考慮例項引數,如multi block i/o、可用排序記憶體的大小等,所以最佳化器有時就選擇了次最佳化的計劃作為真正的執行計劃,導致系統效能不高。
如,對於
select * from emp where deptno = 10;
這個查詢來說,如果是使用基於規則的最佳化器,而且deptno列上有有效的索引,則會透過deptno列上的索引來訪問emp表。在絕大多數情況下,這是比較高效的,但是在一些特殊情況下,使用索引訪問也有比較低效的時候,現舉例說明:
1) emp表比較小,該表的資料只存放在幾個資料塊中。此時使用全表掃描比使用索引訪問emp表反而要好。因為表比較小,極有可能資料全在記憶體中,所以此時做全表掃描是最快的。而如果使用索引掃描,需要先從索引中找到符合條件記錄的rowid,然後再一一根據這些rowid從emp中將資料取出來,在這種條件下,效率就會比全表掃描的效率要差一些。

2) emp表比較大時,而且deptno = 10條件能查詢出表中大部分的資料如(50%)。如該表共有4000萬行資料,共放在有500000個資料塊中,每個資料塊為8k,則該表共有約4G,則這麼多的資料不可能全放在記憶體中,絕大多數需要放在硬碟上。此時如果該查詢透過索引查詢,則是你夢魘的開始。db_file_multiblock_read_count引數的值200。如果採用全表掃描,則需要500000/db_file_multiblock_read_count=500000/200=2500次I/O。但是如果採用索引掃描,假設deptno列上的索引都已經cache到記憶體中,所以可以將訪問索引的開銷忽略不計。因為要讀出4000萬x 50% = 2000萬資料,假設在讀這2000萬資料時,有99.9%的命中率,則還是需要20000次I/O,比上面的全表掃描需要的2500次多多了,所以在這種情況下,用索引掃描反而效能會差很多。在這樣的情況下,用全表掃描的時間是固定的,但是用索引掃描的時間會隨著選出資料的增多使查詢時間相應的延長。

上面是枯燥的假設資料,現在以具體的例項給予驗證:
環境: oracle 817 + linux + 陣列櫃,表SWD_BILLDETAIL有3200多萬資料;
表的id列、cn列上都有索引
經檢視執行計劃,發現執行select count(id) from SWD_BILLDETAIL;使用全表掃描,執行完用了大約1.50分鐘(4次執行取平均,每次分別為1.45 1.51 2.00 1.46)。而執行select count(id) from SWD_BILLDETAIL where cn <'6';卻用了2個小時還沒有執行完,經分析該語句使用了cn列上的索引,然後利用查詢出的rowid再從表中查詢資料。我為什麼不使用select count(cn) from SWD_BILLDETAIL where cn <'6';呢?後面在分析執行路徑的索引掃描時時會給出說明。

下面就是基於規則的最佳化器使用的執行路徑與各個路徑對應的等級:
RBO Path 1: Single Row by Rowid(等級最高)
RBO Path 2: Single Row by Cluster Join
RBO Path 3: Single Row by Hash Cluster Key with Unique or Primary Key
RBO Path 4: Single Row by Unique or Primary Key
RBO Path 5: Clustered Join
RBO Path 6: Hash Cluster Key
RBO Path 7: Indexed Cluster Key
RBO Path 8: Composite Index
RBO Path 9: Single-Column Indexes
RBO Path 10: Bounded Range Search on Indexed Columns
RBO Path 11: Unbounded Range Search on Indexed Columns
RBO Path 12: Sort Merge Join
RBO Path 13: MAX or MIN of Indexed Column
RBO Path 14: ORDER BY on Indexed Column
RBO Path 15: Full Table Scan(等級最低)

上面的執行路徑中,RBO認為越往下執行的代價越大,即等級越低。在RBO生成執行計劃時,如果它發現有等級高的執行路徑可用,則肯定會使用等級高的路徑,而不管任何其它影響效能的元素,即RBO透過上面的路徑的等級決定執行路徑的代價,執行路徑的等級越高,則使用該執行路徑的代價越小。如上面2個例子所述,如果使用RBO,則肯定使用索引訪問表,也就是選擇了比較差的執行計劃,這樣會給資料庫效能帶來很大的負面影響。為了解決這個問題,從ORACLE 7開始oracle引入了基於代價的最佳化器,下面給出了介紹。

基於代價的最佳化器 -- Cost Based Optimization(簡稱CBO)
Oracle把一個代價引擎(Cost Engine)整合到資料庫核心中,用來估計每個執行計劃需要的代價,該代價將每個執行計劃所耗費的資源進行量化,從而CBO可以根據這個代價選擇出最優的執行計劃。一個查詢耗費的資源可以被分成3個基本組成部分:I/O代價、CPU代價、network代價。I/O代價是將資料從磁碟讀入記憶體所需的代價。訪問資料包括將資料檔案中資料塊的內容讀入到SGA的資料快取記憶體中,在一般情況下,該代價是處理一個查詢所需要的最主要代價,所以我們在最佳化時,一個基本原則就是降低查詢所產生的I/O總次數。CPU代價是處理在記憶體中資料所需要的代價,如一旦資料被讀入記憶體,則我們在識別出我們需要的資料後,在這些資料上執行排序(sort)或連線(join)操作,這需要耗費CPU資源。

對於需要訪問跨節點(即通常說的伺服器)資料庫上資料的查詢來說,存在network代價,用來量化傳輸操作耗費的資源。查詢遠端表的查詢或執行分散式連線的查詢會在network代價方面花費比較大。

在使用CBO時,需要有表和索引的統計資料(分析資料)作為基礎資料,有了這些資料,CBO才能為各個執行計劃計算出相對準確的代價,從而使CBO選擇最佳的執行計劃。所以定期的對錶、索引進行分析是絕對必要的,這樣才能使統計資料反映資料庫中的真實情況。否則就會使CBO選擇較差的執行計劃,影響資料庫的效能。分析操作不必做的太頻繁,一般來說,每星期一次就足夠了。切記如果想使用CBO,則必須定期對錶和索引進行分析。

對於分析用的命令,隨著資料庫版本的升級,用的命令也發生了變換,在oracle 8i以前,主要是用ANALYZE命令。在ORACLE 8I以後,又引入了DBMS_STATS儲存包來進行分析。幸運的是從ORACLE 10G以後,分析工作變成自動的了,這減輕的DBA的負擔,不過在一些特殊情況下,還需要一些手工分析。

如果採用了CBO最佳化器,而沒有對錶和索引進行分析,沒有統計資料,則ORACLE使用預設的統計資料(至少在ORACLE 9I中是這樣),這可以從oracle的文件上找到。使用的預設值肯定與系統的實際統計值不一致,這可能會導致最佳化器選擇錯誤的執行計劃,影響資料庫的效能。

要注意的是:雖然CBO的功能隨著ORACLE新版本的推出,功能越來越強,但它不是能包治百病的神藥,否則就不再需要DBA了,那我就慘了!!!實際上任何一個語句,隨著硬體環境與應用資料的不同,該語句的執行計劃可能需要隨之發生變化,這樣才能取得最好的效能。所以有時候不在具體的環境下而進行SQL效能調整是徒勞的。

在ORACLE8I推出的時候,ORACLE極力建議大家使用CBO,說CBO有種種好處,但是在那是ORACLE開發的應用系統還是使用基於規則的最佳化器,從這件事上我們可以得出這樣的結論:1) 如果團隊的資料庫水平很高而且都熟悉應用資料的特點,RBO也可以取得很好的效能。2)CBO不是很穩定,但是一個比較有前途的最佳化器,Oracle極力建議大家用是為了讓大家儘快發現它的BUG,以便進一步改善,但是ORACLE為了對自己開發的應用系統負責,他們還是使用了比較熟悉而且成熟的RBO。從這個事情上給我們的啟發就是:我們在以後的開發中,應該儘量採用我們熟悉並且成熟的技術,而不要一味的採用新技術,一味採用新技術並不一定能開發出好的產品。幸運的是從ORACLE 10G後,CBO已經足夠的強大與智慧,大家可以放心的使用該技術,因為ORACLE 10G後,Oracle自己開發的應用系統也使用CBO最佳化器了。而且ORACLE規定,從ORACLE 10G開始,開始廢棄RBO最佳化器。這句話並不是指在ORACLE 10G中不能使用RBO,而是從ORACLE 10G開始開始,不再為RBO的BUG提供修補服務。

在上面的第2個例子中,如果採用CBO最佳化器,它就會考慮emp表的行數,deptno列的統計資料,發現對該列做查詢會查詢出過多的資料,並且考慮db_file_multiblock_read_count引數的設定,發現用全表掃描的代價比用索引掃描的代價要小,從而使用全表掃描從而取得良好的執行效能。

判斷當前資料庫使用何種最佳化器:
主要是由optimizer_mode初始化引數決定的。該引數可能的取值為:first_rows_[1 | 10 | 100 | 1000] | first_rows | all_rows | choose | rule。具體解釋如下:
RULE為使用RBO最佳化器。
CHOOSE則是根據實際情況,如果資料字典中包含被引用的表的統計資料,即引用的物件已經被分析,則就使用CBO最佳化器,否則為RBO最佳化器。
ALL_ROWS為CBO最佳化器使用的第一種具體的最佳化方法,是以資料的吞吐量為主要目標,以便可以使用最少的資源完成語句。
FIRST_ROWS為最佳化器使用的第二種具體的最佳化方法,是以資料的響應時間為主要目標,以便快速查詢出開始的幾行資料。
FIRST_ROWS_[1 | 10 | 100 | 1000] 為最佳化器使用的第三種具體的最佳化方法,讓最佳化器選擇一個能夠把響應時間減到最小的查詢執行計劃,以迅速產生查詢結果的前 n 行。該引數為ORACLE 9I新引入的。

從ORACLE V7以來,optimizer_mode引數的預設設定應是"choose",即如果對已分析的表查詢的話選擇CBO,否則選擇RBO。在此種設定中,如果採用了CBO,則預設為CBO中的all_rows模式。

注意:即使指定資料庫使用RBO最佳化器,但有時ORACLE資料庫還是會採用CBO最佳化器,這並不是ORACLE的BUG,主要是由於從ORACLE 8I後引入的許多新特性都必須在CBO下才能使用,而你的SQL語句可能正好使用了這些新特性,此時資料庫會自動轉為使用CBO最佳化器執行這些語句。


什麼是最佳化
最佳化是選擇最有效的執行計劃來執行SQL語句的過程,這是在處理任何資料的語句(SELECT,INSERT,UPDATE或DELETE)中的一個重要步驟。對Oracle來說,執行這樣的語句有許多不同的方法,譬如說,將隨著以什麼順序訪問哪些表或索引的不同而不同。所使用的執行計劃可以決定語句能執行得有多快。Oracle中稱之為最佳化器(Optimizer)的元件用來選擇這種它認為最有效的執行計劃。

由於一系列因素都會會影響語句的執行,最佳化器綜合權衡各個因素,在眾多的執行計劃中選擇認為是最佳的執行計劃。然而,應用設計人員通常比最佳化器更知道關於特定應用的資料特點。無論最佳化器多麼智慧,在某些情況下開發人員能選擇出比最佳化器選擇的最優執行計劃還要好的執行計劃。這是需要人工干預資料庫最佳化的主要原因。事實表明,在某些情況下,確實需要DBA對某些語句進行手工最佳化。

注:從Oracle的一個版本到另一個版本,最佳化器可能對同一語句生成不同的執行計劃。在將來的Oracle 版本中,最佳化器可能會基於它可以用的更好、更理想的資訊,作出更優的決策,從而導致為語句產生更優的執行計劃。

[@more@]

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/25462274/viewspace-1983070/,如需轉載,請註明出處,否則將追究法律責任。

相關文章