使用Oracle執行計划分析SQL性能


執行計划:一條查詢語句在ORACLE中的執行過程或訪問路徑的描述。即就是對一個查詢任務,做出一份怎樣去完成任務的詳細方案。

如果要分析某條SQL的性能問題,通常我們要先看SQL的執行計划,看看SQL的每一步執行是否存在問題。 看懂執行計划也就成了SQL優化的先決條件。 通過執行計划定位性能問題,定位后就通過建立索引、修改sql等解決問題。

一、執行計划的查看

1.1 設置autotrace

autotrace命令如下

序號

命令

解釋

1

SET AUTOTRACE OFF

此為默認值,即關閉Autotrace

2

SET AUTOTRACE ON EXPLAIN

只顯示執行計划

3

SET AUTOTRACE ON STATISTICS

只顯示執行的統計信息

4

SET AUTOTRACE ON

包含2,3兩項內容

5

SET AUTOTRACE TRACEONLY

ON相似,但不顯示語句的執行結果

1.2 使用SQL

在執行的sql前面加上EXPLAIN PLAN FOR

復制代碼
SQL> EXPLAIN PLAN FOR SELECT * FROM EMP;

已解釋。

SQL> SELECT plan_table_output FROM TABLE(DBMS_XPLAN.DISPLAY('PLAN_TABLE'));

或者:

SQL> select * from table(dbms_xplan.display);
復制代碼

1.3 使用PL/SQL Developer,Navicat, Toad等客戶端工具

 

二、如何讀懂執行計划

2.1執行順序的原則

執行順序的原則是:由上至下,從右向左
由上至下:在執行計划中一般含有多個節點,相同級別(或並列)的節點,靠上的優先執行,靠下的后執行
從右向左:在某個節點下還存在多個子節點,先從最靠右的子節點開始執行。

一般按縮進長度來判斷,縮進最大的最先執行,如果有2行縮進一樣,那么就先執行上面的。

 

以下面的sql為例(sakila樣例數據庫中的address city country連接查詢)

select address.address, city.city, country.country
from address
inner join city on address.city_id = city.city_id
inner join country on city.country_id = country.country_id;

clip_image006

2.2 執行計划中字段解釋

ID: 一個序號,但不是執行的先后順序。執行的先后根據縮進來判斷。

Operation: 當前操作的內容。

Rows: 當前操作的Cardinality,Oracle估計當前操作的返回結果集。

Cost(CPU):Oracle 計算出來的一個數值(代價),用於說明SQL執行的代價。

Time:Oracle 估計當前操作的時間。

在看執行計划的時候,除了看執行計划本身,還需要看謂詞和統計信息。 通過整體信息來判斷SQL效率。

2.3 謂詞說明

Access :

  • 通過某種方式定位了需要的數據,然后讀取出這些結果集,叫做Access。
  • 表示這個謂詞條件的值將會影響數據的訪問路勁(表還是索引)。

Filter:

  • 把所有的數據都訪問了,然后過濾掉不需要的數據,這種方式叫做filter 。
  • 表示謂詞條件的值不會影響數據的訪問路勁,只起過濾的作用。

在謂詞中主要注意access,要考慮謂詞的條件,使用的訪問路徑是否正確。

2.4 Statistics(統計信息)說明

recursive calls

產生的遞歸sql調用的條數。

 

Db block gets:

buffer cache中讀取的block的數量

consistent gets

buffer cache中讀取的undo數據的block的數量   

physical reads

從磁盤讀取的block的數量  

redo size

DML生成的redo的大小   

bytes sent via SQL*Net to client

數據庫服務器通過SQL*Net向查詢客戶端發送的查詢結果字節數

bytes received via SQL*Net from client

通過SQL*Net接受的來自客戶端的數據字節數

SQL*Net roundtrips to/from client

服務器和客戶端來回往返通信的Oracle Net messages條數

sorts (memory)

在內存執行的排序量   

sorts (disk)

在磁盤上執行的排序量

rows processed

處理的數據的行數

解釋:

Recursive CallsNumber of recursive calls generated at both the user and system level.

Oracle Database maintains tables used for internal processing. When it needs to change these tables, Oracle Database generates an internal SQL statement, which in turn generates a recursive call. In short, recursive calls are basically SQL performed on behalf of your SQL. So, if you had to parse the query, for example, you might have had to run some other queries to get data dictionary information. These would be recursive calls. Space management, security checks, calling PL/SQL from SQL—all incur recursive SQL calls。

當執行一條SQL語句時,產生的對其他SQL語句的調用,這些額外的語句稱之為''recursive calls''或''recursive SQL statements''. 我們做一條insert 時,沒有足夠的空間來保存row記錄,Oracle 通過Recursive Call 來動態的分配空間。

DB Block Gets:Number of times a CURRENT block was requested.

Current mode blocks are retrieved as they exist right now, not in a consistent read fashion. Normally, blocks retrieved for a query are retrieved as they existed when the query began. Current mode blocks are retrieved as they exist right now, not from a previous point in time. During a SELECT, you might see current mode retrievals due to reading the data dictionary to find the extent information for a table to do a full scan (because you need the "right now" information, not the consistent read). During a modification, you will access the blocks in current mode in order to write to them.

DB Block Gets:請求的數據塊在buffer能滿足的個數

當前模式塊意思就是在操作中正好提取的塊數目,而不是在一致性讀的情況下而產生的塊數。正常的情況下,一個查詢提取的塊是在查詢開始的那個時間點上存在的數據塊,當前塊是在這個時刻存在的數據塊,而不是在這個時間點之前或者之后的數據塊數目。

Consistent Gets: Number of times a consistent read was requested for a block.

This is how many blocks you processed in "consistent read" mode. This will include counts of blocks read from the rollback segment in order to roll back a block. This is the mode you read blocks in with a SELECT, for example. Also, when you do a searched UPDATE/DELETE, you read the blocks in consistent read mode and then get the block in current mode to actually do the modification.

(Consistent Gets: 數據請求總數在回滾段Buffer中的數據一致性讀所需要的數據塊)

這里的概念是在處理你這個操作的時候需要在一致性讀狀態上處理多少個塊,這些塊產生的主要原因是因為由於在你查詢的過程中,由於其他會話對數據塊進行操作,而對所要查詢的塊有了修改,但是由於我們的查詢是在這些修改之前調用的,所以需要對回滾段中的數據塊的前映像進行查詢,以保證數據的一致性。這樣就產 生了一致性讀。

Physical Reads:

Total number of data blocks read from disk. This number equals the value of "physical reads direct" plus all reads into buffer cache.

(Physical Reads:實例啟動后,從磁盤讀到Buffer Cache數據塊數量)

就是從磁盤上讀取數據塊的數量,其產生的主要原因是:

(1) 在數據庫高速緩存中不存在這些塊

(2) 全表掃描

(3) 磁盤排序

它們三者之間的關系大致可概括為:

邏輯讀指的是Oracle從內存讀到的數據塊數量。一般來說是'consistent gets' + 'db block gets'。當在內存中找不到所需的數據塊的話就需要從磁盤中獲取,於是就產生了'physical reads'。

Physical Reads通常是我們最關心的,如果這個值很高,說明要從磁盤請求大量的數據到Buffer Cache里,通常意味着系統里存在大量全表掃描的SQL語句,這會影響到數據庫的性能,因此盡量避免語句做全表掃描,對於全表掃描的SQL語句,建議增 加相關的索引,優化SQL語句來解決。

關於physical reads ,db block gets 和consistent gets這三個參數之間有一個換算公式:

數據緩沖區的使用命中率=1 - ( physical reads / (db block gets + consistent gets) )。

用以下語句可以查看數據緩沖區的命中率:

SQL>SELECT name, value FROM v$sysstat WHERE name IN ('db block gets', 'consistent gets','physical reads');

查詢出來的結果Buffer Cache的命中率應該在90%以上,否則需要增加數據緩沖區的大小。

復制代碼
清空Buffer Cache和數據字典緩存

SQL> alter system flush shared_pool;  //請勿隨意在生產環境執行此語句  
 
System altered  
 
SQL> alter system flush buffer_cache;  //請勿隨意在生產環境執行此語句  
 
System altered  
復制代碼

bytes sent via SQL*Net to client:

    Total number of bytes sent to the client from the foreground processes.

bytes received via SQL*Net from client:

    Total number of bytes received from the client over Oracle Net.

SQL*Net roundtrips to/from client:

Total number of Oracle Net messages sent to and received from the client.

Oracle Net是把Oracle網絡粘合起來的粘合劑。它負責處理客戶到服務器和服務器到客戶通信,

sorts (memory): 在內存里排序。

Number of sort operations that were performed completely in memory and did not require any disk writes

You cannot do much better than memory sorts, except maybe no sorts at all. Sorting is usually caused by selection criteria specifications within table join SQL operations.

Sorts(disk): 在磁盤上排序。

    Number of sort operations that required at least one disk write. Sorts that require I/O to disk are quite resource intensive. Try increasing the size of the initialization parameter SORT_AREA_SIZE.

所有的sort都是優先在memory中做的,當要排序的內容太多,在sort area中放不下的時候,會需要臨時表空間,產生sorts(disk)

 

2.5 動態分析

動態統計量收集是Oracle CBO優化器的一種特性。優化器生成執行計划是依據成本cost公式計算出的,如果相關數據表沒有收集過統計量,又要使用CBO的機制,就會引起動態采樣。

動態采樣(dynamic sampling)就是在生成執行計划是,以一個很小的采用率現進行統計量收集。由於采樣率低,采樣過程快但是不精確,而且采樣結果不會進入到數據字典中。

如果在執行計划中有如下提示:

Note
-------------dynamic sampling used for the statement

這提示用戶CBO當前使用的技術,需要用戶在分析計划時考慮到這些因素。 當出現這個提示,說明當前表使用了動態采樣。 我們從而推斷這個表可沒有做過分析。

這里會出現兩種情況:

(1) 如果表沒有做過分析,那么CBO可以通過動態采樣的方式來獲取分析數據,也可以或者正確的執行計划。

(2) 如果表分析過,但是分析信息過舊,這時CBO就不會在使用動態采樣,而是使用這些舊的分析數據,從而可能導致錯誤的執行計划。

 

三、JOIN方式

在多表聯合查詢的時候,如果我們查看它的執行計划,就會發現里面有多表之間的連接方式。多表之間的連接有三種方式:Nested Loops,Hash Join 和 Sort Merge Join.具體適用哪種類型的連接取決於

  • 當前的優化器模式 (ALL_ROWS 和 RULE)
  • 取決於表大小
  • 取決於連接列是否有索引
  • 取決於連接列是否排序

3.1 hash join

Hash join散列連接是CBO 做大數據集連接時的常用方式,優化器使用兩個表中較小的表(通常是小一點的那個表或數據源)利用連接鍵(JOIN KEY)在內存中建立散列表,將列數據存儲到hash列表中,然后掃描較大的表,同樣對JOIN KEY進行HASH后探測散列表,找出與散列表匹配的行。需要注意的是:如果HASH表太大,無法一次構造在內存中,則分成若干個partition,寫入磁盤的temporary segment,則會多一個寫的代價,會降低效率。

這種方式適用於較小的表完全可以放於內存中的情況,這樣總成本就是訪問兩個表的成本之和。但是在表很大的情況下並不能完全放入內存,這時優化器會將它分割成若干不同的分區,不能放入內存的部分就把該分區寫入磁盤的臨時段,此時要有較大的臨時段從而盡量提高I/O 的性能。

可以用USE_HASH(table_name1 table_name2)提示來強制使用散列連接。

使用情況:Hash join在兩個表的數據量差別很大的時候.

3.2 merge join

Merge Join 是先將關聯表的關聯列各自做排序,然后從各自的排序表中抽取數據,到另一個排序表中做匹配。

因為merge join需要做更多的排序,所以消耗的資源更多。 通常來講,能夠使用merge join的地方,hash join都可以發揮更好的性能,即散列連接的效果都比排序合並連接要好。然而如果行源已經被排過序,在執行排序合並連接時不需要再排序了,這時排序合並連接的性能會優於散列連接。

可以使用USE_MERGE(table_name1 table_name2)來強制使用排序合並連接.

適用情況:

1.RBO模式

2.不等價關聯(>,<,>=,<=,<>)

3.HASH_JOIN_ENABLED=false

4. 用在沒有索引,並且數據已經排序的情況.

3.3 nested loop

Nested loops 工作方式是循環從一張表中讀取數據(驅動表outer table),然后訪問另一張表(被查找表 inner table,通常有索引)。驅動表中的每一行與inner表中的相應記錄JOIN。類似一個嵌套的循環。

對於被連接的數據子集較小的情況,嵌套循環連接是個較好的選擇。在嵌套循環中,內表被外表驅動,外表返回的每一行都要在內表中檢索找到與它匹配的行,因此整個查詢返回的結果集不能太大(大於1 萬不適合),要把返回子集較小表的作為外表(CBO 默認外表是驅動表),而且在內表的連接字段上一定要有索引。當然也可以用ORDERED 提示來改變CBO默認的驅動表。

使用USE_NL(table_name1 table_name2)可是強制CBO 執行嵌套循環連接。

適用情況:

適用於驅動表的記錄集比較小(<10000)而且inner表需要有有效的訪問方法(Index),並且索引選擇性較好的時候.

JOIN的順序很重要,驅動表的記錄集一定要小,返回結果集的響應時間是最快的。

四、表訪問方式

4.1表訪問方式---->全表掃描(Full Table Scans)

全表掃描是指Oracle在訪問目標表里的數據時,會從該表所占用的第一個區(EXTENT)的第一個塊(BLOCK)開始掃描,一直掃描到該表的高水位線(HWM,High Water Mark),Oracle會對這期間讀到的所有數據施加目標SQL的where條件中指定的過濾條件,最后只返回那些滿足過濾條件的數據。

不是說全表掃描不好,事實上Oracle在做全表掃描操作時會使用多塊讀,ORACLE采用一次讀入多個數據塊 (database block)的方式優化全表掃描,而不是只讀取一個數據塊,這極大的減少了I/O總次數,提高了系統的吞吐量,所以利用多塊讀的方法可以十分高效地實現全表掃描。這在目標表的數據量不大時執行效率是非常高的,但全表掃描最大的問題就在於走全表掃描的目標SQL的執行時間會不穩定、不可控,這個執行時間一定會隨着目標表數據量的遞增而遞增。因為隨着目標表數據量的遞增,它的高水位線會一直不斷往上漲,所以全表掃描該表時所需要讀取的數據塊的數量也會不斷增加,這意味着全表掃描該表時所需要耗費的I/O資源會隨之不斷增加,當然完成對該表的全表掃描操作所需要耗費的時間也會隨之增加。

在Oracle中,如果對目標表不停地插入數據,當分配給該表的現有空間不足時高水位線就會向上移動,但如果你用DELETE語句從該表刪除數據, 則高水位線並不會隨之往下移動(這在某種程度上契合了"高水位線"的定義,就好比水庫的水位,當水庫漲水時,水位會往上移,當水庫放水后,曾經的最高水位 的痕跡還是會清晰可見)。高水位線的這種特性所帶來的副作用是,即使使用DELETE語句刪光了目標表中的所有數據,高水位線還是會在原來的位置,這意味着全表掃描該表時Oracle還是需要掃描該表高水位線下的所有數據塊,所以此時對該表的全表掃描操作所耗費的時間與之前相比並不會有明顯的改觀。

使用FTS的前提條件:在較大的表上不建議使用全表掃描,除非取出數據的比較多,超過總量的5% -- 10%,或你想使用並行查詢功能時。

4.2表訪問方式---->通過ROWID訪問表(table access by ROWID)

ROWID是一個偽列,即是一個非用戶定義的列,而又實際存儲於數據庫之中。每一個表都有一個ROWID列,一個ROWID值用於唯一確定數據庫表中的的一條記錄。因此通過ROWID 方式來訪問數據也是 Oracle 數據庫訪問數據的實現方式之一。一般情況下,ROWID方式的訪問一定以索引訪問或用戶指定ROWID作為先決條件,因為所有的索引訪問方式最終都會轉換為通過ROWID來訪問數據記錄。(注:index full scan 與index fast full scan除外)由於Oracle ROWID能夠直接定位一條記錄,因此使用ROWID方式來訪問數據,極大提高數據的訪問效率

ROWID掃描是指Oracle在訪問目標表里的數據時,直接通過數據所在的ROWID去定位並訪問這些數據。

從嚴格意義上來說,Oracle中的ROWID掃描有兩層含義:一種是根據用戶在SQL語句中輸入的ROWID的值直接去訪問對應的數據行記錄;另外一種是先去訪問相關的索引,然后根據訪問索引后得到的ROWID再回表去訪問對應的數據行記錄。

對Oracle中的堆表而言,我們可以通過Oracle內置的ROWID偽列得到對應行記錄所在的ROWID的值(注意,這個ROWID只是一個偽 列,在實際的表塊中並不存在該列),然后我們還可以通過DBMS_ROWID包中的相關方法(dbms_rowid.rowid_object,dbms_rowid.rowid_relative_fno、dbms_rowid.rowid_block_number和 dbms_rowid.rowid_row_number)將上述ROWID偽列的值翻譯成對應數據行的實際物理存儲地址。

4.3索引掃描

 索引范圍掃描(INDEX RANGE SCAN)適用於所有類型的B樹索引,當掃描的對象是唯一性索引時,此時目標SQL的where條件一定是范圍查詢(謂詞條件為 BETWEEN、<、>等);當掃描的對象是非唯一性索引時,對目標SQL的where條件沒有限制(可以是等值查詢,也可以是范圍查詢)。 索引范圍掃描的結果可能會返回多條記錄,其實這就是索引范圍掃描中"范圍"二字的本質含義。

一:唯一索引的范圍查詢

二:非唯一索引的等值查詢

三:非唯一索引的范圍查詢

索引唯一性掃描(INDEX UNIQUE SCAN)是針對唯一性索引(UNIQUE INDEX)的掃描,它僅僅適用於where條件里是等值查詢的目標SQL。因為掃描的對象是唯一性索引,所以索引唯一性掃描的結果至多只會返回一條記錄。

所謂的索引全掃描(INDEX FULL SCAN)就是指要掃描目標索引所有葉子塊的所有索引行。這里需要注意的是,索引全掃描需要掃描目標索引的所有葉子塊,但這並不意味着需要掃描該索引的所有分支塊。在默認情況下,Oracle在做索引全掃描時只需要通過訪問必要的分支塊定位到位於該索引最左邊的葉子塊的第一行索引行,就可以利用該索引葉子塊之間的雙向指針鏈表,從左至右依次順序掃描該索引所有葉子塊的所有索引行了。

索引快速全掃描(INDEX FAST FULL SCAN)和索引全掃描(INDEX  FULL SCAN)極為類似,它也適用於所有類型的B樹索引(包括唯一性索引和非唯一性索引)。和索引全掃描一樣,索引快速全掃描也需要掃描目標索引所有葉子塊的所有索引行。

索引快速全掃描與索引全掃描相比有如下三點區別。

(1)索引快速全掃描只適用於CBO。

(2)索引快速全掃描可以使用多塊讀,也可以並行執行。

(3)索引快速全掃描的執行結果不一定是有序的。這是因為索引快速全掃描時Oracle是根據索引行在磁盤上的物理存儲順序來掃描,而不是根據索引行的邏輯順序來掃描的,所以掃描結果才不一定有序(對於單個索引葉子塊中的索引行而言,其物理存儲順序和邏輯存儲順序一致;但對於物理存儲位置相鄰的索引葉子塊而言,塊與塊之間索引行的物理存儲順序則不一定在邏輯上有序)。

索引跳躍式掃描(INDEX SKIP SCAN)適用於所有類型的復合B樹索引(包括唯一性索引和非唯一性索引),它使那些在where條件中沒有對目標索引的前導列指定查詢條件但同時又對該 索引的非前導列指定了查詢條件的目標SQL依然可以用上該索引,這就像是在掃描該索引時跳過了它的前導列,直接從該索引的非前導列開始掃描一樣(實際的執行過程並非如此),這也是索引跳躍式掃描中"跳躍"(SKIP)一詞的含義。

為什么在where條件中沒有對目標索引的前導列指定查詢條件但Oracle依然可以用上該索引呢?這是因為Oracle幫你對該索引的前導列的所有distinct值做了遍歷。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM