日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

Oracle优化05-执行计划

發(fā)布時(shí)間:2025/3/21 编程问答 31 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Oracle优化05-执行计划 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

思維導(dǎo)圖


系列文章

Oracle-SQL Explain Plan解讀

概述

如果要分析某條SQL的性能問題,通常來講,我們首先要看SQL的執(zhí)行計(jì)劃,看看SQL的每一步執(zhí)行計(jì)劃是否存在問題。

如果某一條SQL平常執(zhí)行的都很好,卻有一天突然性能很差,如果排除了系統(tǒng)資源和阻塞的原因,那么基本上可以判斷是執(zhí)行計(jì)劃出現(xiàn)了問題。

看懂執(zhí)行計(jì)劃變成了SQL優(yōu)化(其實(shí)在大多數(shù)的情況下,SQL優(yōu)化指的是SQL的性能問題定位)的先決條件。

在討論SQL執(zhí)行計(jì)劃之前,我們需要知道執(zhí)行計(jì)劃當(dāng)中一個(gè)非常重要的概念–Cardinality基數(shù)。


Cardinality基數(shù)

在我們看執(zhí)行計(jì)劃的每一步操作的時(shí)候,當(dāng)前操作的Cardinality值表示CBO預(yù)期從一個(gè)行源(row source)返回的記錄數(shù)。

一個(gè)行源可能是一個(gè)表、一個(gè)索引、也可能是一個(gè)子查詢。

比如:


當(dāng)CBO無法準(zhǔn)確的獲取到Cardinality時(shí),將會(huì)發(fā)生什么?

在執(zhí)行計(jì)劃中, card 就是Cardinality的縮寫,它表示CBO估算當(dāng)前操作預(yù)期獲取的記錄數(shù)。

Cardinality的值對(duì)于CBO做出正確的執(zhí)行計(jì)劃來說至關(guān)重要,如果CBO獲得的Cardinality值不夠準(zhǔn)確(通常是沒有做分析或者分析數(shù)據(jù)過舊導(dǎo)致),在執(zhí)行成本計(jì)算上就會(huì)出現(xiàn)偏差,從而導(dǎo)致CBO錯(cuò)誤的制定出執(zhí)行計(jì)劃。

下面演示下當(dāng)CBO無法準(zhǔn)確的獲取到Cardinality時(shí),將會(huì)發(fā)生什么?

創(chuàng)建一個(gè)數(shù)據(jù)分布非常不均勻的表T

Connected to Oracle Database 11g Enterprise Edition Release 11.2.0.4.0 Connected as xxx@xgjSQL> drop table t;Table droppedSQL> create table t as select 1 id , object_name from dba_objects;Table createdSQL> update t set t.id=99 where rownum=1;1 row updatedSQL> commit;Commit completeSQL> create index t_ind on t(id);Index createdSQL> select id ,count(*) from t group by id ;ID COUNT(*) ---------- ----------1 3525199 1

查看執(zhí)行計(jì)劃

##執(zhí)行SQL SQL> select /*+ dynamic_sampling(t 0) */ * from t where id=1;SQL> SELECT sql_id, child_number, a.* from v$sql a where a.SQL_TEXT like '%select /*+ dynamic_sampling(t 0) */ * from t where id=1%';SQL> select * from table(DBMS_XPLAN.display_cursor('bb22rwn4604yj',0));

說明:

  • /+ dynamic_sampling(t 0) / :目的是讓CBO無法通過動(dòng)態(tài)采樣獲取表中實(shí)際數(shù)據(jù)的情況,此時(shí)CBO只能根據(jù)數(shù)據(jù)字典中標(biāo)T的非常有限的信息(比如表的extends數(shù)量,數(shù)據(jù)塊的數(shù)量)來猜測(cè)表中的數(shù)據(jù)
  • 從結(jié)果中可以看出,CBO猜測(cè)出id=1的數(shù)據(jù)為118條,而與實(shí)際上的數(shù)量 35251,相差甚遠(yuǎn),所以CBO選擇了索引而不是全表掃描

讓我們看下實(shí)際的執(zhí)行情況:

select * from t where id=1;SELECT sql_id, child_number, a.* from v$sql a where a.SQL_TEXT like '%select * from t where id=1%';select * from table(DBMS_XPLAN.display_cursor('3k9f6qpq9sbmn',0));

通過動(dòng)態(tài)采樣(10g及以上版本,如果表沒有做過分析,ORACLE會(huì)自動(dòng)通過動(dòng)態(tài)采樣的方式來收集分析數(shù)據(jù)),CBO估算出來的表中數(shù)量為39257 (Cardinality) 和實(shí)際的數(shù)量 35251非常接近,CBO判斷ID=1的數(shù)據(jù)基本上等同于表中的數(shù)據(jù),所以選擇了全表掃描。


下面我們做一下表和索引分析

##先查看下 SQL> select num_rows, avg_row_len, blocks, last_analyzed from user_tables where table_name = 'T';NUM_ROWS AVG_ROW_LEN BLOCKS LAST_ANALYZED ---------- ----------- ---------- -------------SQL> select blevel,leaf_blocks,distinct_keys,last_analyzed from user_indexes where table_name='T';BLEVEL LEAF_BLOCKS DISTINCT_KEYS LAST_ANALYZED ---------- ----------- ------------- -------------##對(duì)表和索引做分析 SQL> exec dbms_stats.gather_table_stats(user,'t',cascade => true);PL/SQL procedure successfully completed##重新查詢信息 SQL> select num_rows, avg_row_len, blocks, last_analyzed from user_tables where table_name = 'T';NUM_ROWS AVG_ROW_LEN BLOCKS LAST_ANALYZED ---------- ----------- ---------- -------------35252 22 144 2016-12-30 0:SQL> select blevel,leaf_blocks,distinct_keys,last_analyzed from user_indexes where table_name='T';BLEVEL LEAF_BLOCKS DISTINCT_KEYS LAST_ANALYZED ---------- ----------- ------------- -------------1 69 2 2016-12-30 0:SQL>

查看執(zhí)行計(jì)劃

SQL> select * from t a where a.id=99;SQL> SELECT sql_id, child_number, a.* from v$sql a where a.SQL_TEXT like '%select * from t a where a.id=99%';SQL> select * from table(DBMS_XPLAN.display_cursor('fa0r3kc8y5239',0));

通過對(duì)表的分析,CBO就可以獲取到T表和索引的充足的信息。

上面的截圖,CBO從分析數(shù)據(jù)中,獲取到了id=99的數(shù)據(jù)6 rows , 所以選擇了索引。

我們更新下數(shù)據(jù)

SQL>update t set id=99; SQL>commit;

將id 全部更新為99 , 因?yàn)闆]有對(duì)表進(jìn)行分析,所以CBO知道的信息還是舊的,重新查詢 下 我們可以看到 CBO依然認(rèn)為表T中的數(shù)據(jù)很少,依然選擇的是索引。

SQL> exec dbms_stats.gather_table_stats(user,'t',cascade => true);PL/SQL procedure successfully completed ## 如果使用 DBMS_XPLAN.display_cursor 查詢執(zhí)行計(jì)劃的話,需要清空shared_pool ,或者換個(gè)SQL(目的是不匹配到shared_pool中的緩存) SQL> alter system flush shared_pool;System altered##重新執(zhí)行SQL SQL> select * from t a where a.id=99;##獲取SQL_ID等 SQL> SELECT sql_id, child_number, a.* from v$sql a where a.SQL_TEXT like '%select * from t a where a.id=99%';##查看執(zhí)行計(jì)劃 SQL> select * from table(DBMS_XPLAN.display_cursor('fa0r3kc8y5239',0));

重新對(duì)表分析后,CBO獲取了正確的Cardinality值。T表中id=99的數(shù)據(jù)3w+,所以全表掃描是最佳執(zhí)行計(jì)劃。



Cardinality是如何影響多表查詢的?

在多表關(guān)聯(lián)查詢或者SQL中有子查詢時(shí),每個(gè)關(guān)聯(lián)表或者子查詢的Cardinality的值對(duì)主查詢的影響非常大,甚至可以說,CBO就是依賴于各個(gè)關(guān)聯(lián)表或者子查詢Cardinality值來計(jì)算出最后的執(zhí)行計(jì)劃。

對(duì)于多表查詢,CBO使用每個(gè)關(guān)聯(lián)表返回的行數(shù)(Cardinality)決定使用設(shè)么樣的方式來做表關(guān)聯(lián)(比如Nested loops ,sm 或者 hash join),

對(duì)于子查詢,它的Cardinality將決定子查詢是使用索引還是使用全表掃描的方式訪問數(shù)據(jù)。

舉例說明:

數(shù)據(jù)如下:

Connected to Oracle Database 11g Enterprise Edition Release 11.2.0.4.0 Connected as xxx@xgjSQL> create table t1 (id int , name varchar2(1000));Table createdSQL> create table t2 (id int , name varchar2(1000));Table createdSQL> create index ind_t1 on t1(id);Index createdSQL> create index ind_t2 on t2(id);Index createdSQL> create index ind_t2_name on t2(name);Index createdSQL> insert into t1 select object_id ,object_name from dba_objects ;35258 rows insertedSQL> commit;Commit completeSQL> insert into t2 values(1,'XGJ');1 row insertedSQL> commit;Commit complete##僅對(duì)t1的表和索引進(jìn)行分析 SQL> exec dbms_stats.gather_table_stats(user,'t1',cascade => true,method_opt => 'for all indexed columns');PL/SQL procedure successfully completedSQL> select * from t1 where id in (select /*+ dynamic_sampling(t2 0) cardinality(t2 10000) */ id from t2 where name = 'XGJ');ID NAME ------------- ----------------------SQL>select * from v$sql a where a.SQL_TEXT like '% /*+ dynamic_sampling(t2 0) cardinality(t2 10000) */ %';##DBMS_XPLAN.display_cursor中查看執(zhí)行計(jì)劃 SQL>select * from table(dbms_xplan.display_cursor('2c7hx20a3bhxx',0));

解析:

我們發(fā)出的子查詢的SQL,同時(shí)使用了hint

/*+ dynamic_sampling(t2 0) cardinality(t2 10000) */
  • dynamic_sampling(t2 0):禁止動(dòng)態(tài)采用
  • cardinality(t2 10000):告訴CBO從T2表中取10000條記錄

通過這種方式,我們模擬子查詢中返回的結(jié)果數(shù),同時(shí)為了讓CBO完全依賴這個(gè)信息生成執(zhí)行計(jì)劃,我們禁止了子查詢使用動(dòng)態(tài)采樣(dynamic_sampleing 設(shè)置為0)。

可以看到,當(dāng)CBO得到來自于子查詢的結(jié)果集(Rows )的記錄為10000條時(shí),采用了hash join semi的執(zhí)行計(jì)劃,hash join通常適用于兩張關(guān)聯(lián)的表都比較大的時(shí)候


如果我們把子查詢的結(jié)果集變得很小會(huì)怎樣呢?

來看我們下面的栗子:

SQL> select * from t1 where id in (select /*+ dynamic_sampling(t2 0) cardinality(t2 1) */ id from t2 where name = 'XGJ');ID NAME ------ ----------

同樣的 我們來看下執(zhí)行計(jì)劃:

我們?cè)诓樵冎袑⒆硬樵兊姆祷刂翟O(shè)置為1,即

Cardinality(t2,1)

此時(shí)CBO選擇了兩個(gè)表通過nested loop join 進(jìn)行關(guān)聯(lián)的執(zhí)行計(jì)劃,因?yàn)樽硬樵冎挥?條記錄,這個(gè)時(shí)候CBO會(huì)選擇最適合擇偶張情況的netsted loops join關(guān)聯(lián)方式。


從這個(gè)試驗(yàn)中我們可以得到如下結(jié)論:

子查詢的Cardinality的值,直接影響了主查詢的執(zhí)行計(jì)劃,如果CBO對(duì)子查詢的Cardinality判斷有誤,那么餓主查詢的執(zhí)行計(jì)劃很有可能是錯(cuò)誤的。


再看量表關(guān)聯(lián)的查詢情況,還是用上面的栗子

##上面執(zhí)行后 user_index有值了...清掉先 select num_rows, avg_row_len, blocks, last_analyzed from user_tables where table_name = 'T2';select blevel,leaf_blocks,distinct_keys,last_analyzed from user_indexes where table_name='T2';SQL> analyze table t2 delete statistic2 ;Table analyzedSQL> alter system flush shared_pool;System alteredSQL> select /*+ dynamic_sampling(t2 0) cardinality(t2 10000) */ * from t1,t2 where t1.id=t2.id;....省略輸出

查看執(zhí)行計(jì)劃

這個(gè)栗子中,CBO認(rèn)為T2關(guān)聯(lián)的數(shù)據(jù)足夠多,而且T1又足夠大,所以在這種情況下,hash join 是最合適的。

SQL>select /*+ dynamic_sampling(t2 0) cardinality(t2 1) */ * from t1,t2 where t1.id=t2.id;....省略輸出

查看執(zhí)行計(jì)劃:

這時(shí)候因?yàn)門2給CBO提供的信息,只有1條記錄做關(guān)聯(lián)查詢,所以CBO選擇了nested loop join .

總結(jié)

以上的例子主要說明Cardinality對(duì)CBO生成執(zhí)行計(jì)劃的影響,所以我們?cè)诳炊啾聿樵兊臅r(shí)候,一定要注意每個(gè)操作返回的Cardinality的值,如果這個(gè)值明顯的不對(duì),那么很有可能操作的表的分析數(shù)據(jù)出了問題,或者沒有分析。

比如在上面的栗子中,我們確切的知道T2表的數(shù)據(jù)很小,而在執(zhí)行計(jì)劃中卻顯示10000條,這顯然不對(duì)了,這個(gè)時(shí)候叫就要檢查問題所在,看看T2表是不是曾經(jīng)有很多數(shù)據(jù),刪除之后沒有做重新分析等等。


SQL的執(zhí)行計(jì)劃

如果一條SQL的性能出現(xiàn)了問題,首先應(yīng)該看一下它的執(zhí)行計(jì)劃,以便確定(或者猜測(cè))問題的所在。

生成SQL的執(zhí)行計(jì)劃時(shí)Oracle在對(duì)SQL做硬分析時(shí)的一個(gè)非常重要的步驟,它制定出一個(gè)方案告訴Oracle在執(zhí)行這條SQL時(shí)以什么樣的方式訪問數(shù)據(jù): 索引掃描? 全表掃描? , 是hash join 還是 netsted loops join 等。


如何得到一個(gè)SQL的執(zhí)行計(jì)劃

  • explain plan for
  • SQLPLUS命令 set autotrace on
  • 第三方軟件提供的GUI工具,比如Toad ,PL/SQL Developer

具體看: Oracle-SQL Explain Plan解讀


如何看懂一個(gè)SQL的執(zhí)行計(jì)劃

首先得到一個(gè)SQL的執(zhí)行計(jì)劃

我們使用select * from table(DBMS_XPLAN.display_cursor(.......))的方式獲取select * from t1 ,t2 where t1.id = t2.id如下的執(zhí)行計(jì)劃:

其中,rows列 就是我們上面說到的 Card(Cardinality) 9i以前的版本使用的是Card.


如何閱讀呢?

我們首先從說縮進(jìn)度最大的行讀取,它是最先被執(zhí)行的步驟

ID=3和ID=4是最先被執(zhí)行的。

當(dāng)兩行縮進(jìn)一樣時(shí),最上面的最先被執(zhí)行,在這里就是 ID=3的先執(zhí)行,然后是ID=4的。

然后是縮進(jìn)次之的,

這里就是

緊接著鎖進(jìn)再次之的,以此類推

在這里就是是ID=1的 ,最后是ID=0的。

我們也可在PL/SQL中F5查看執(zhí)行計(jì)劃后,一步步的跟蹤

這就是這個(gè)SQL的執(zhí)行過程。

大致意思是:

從T2表讀取第一行數(shù)據(jù) 是否符合條件

如果符合就拿出一行來,然后到索引IND_T1 中找到對(duì)應(yīng)的值,然后重復(fù),直到把整個(gè)T2表全表掃描完,這個(gè)過程就叫NESTED LOOPS .

當(dāng)T2表被掃描完之后,會(huì)產(chǎn)生一個(gè)結(jié)果集,這個(gè)結(jié)果集是 IND_T1的一個(gè)索引集,然后ORACLE根據(jù)說印鍵值上的rowid 去T1表找對(duì)應(yīng)的記錄,就是這一步: Operation TABLE ACCESS BY INDEX ROWID

然后還有個(gè)NESTED LOOPS .(疑惑待思考)

最后將結(jié)果返回: Operation SELECT STATEMENT


執(zhí)行計(jì)劃中的值說明

  • ID列: 是一個(gè)序號(hào),注意,它的大小并不是執(zhí)行的先后順序。

  • Operation列: 是當(dāng)前的操作內(nèi)容。

  • Rows 列: 就是當(dāng)前操作的cardinality,Oracle估算當(dāng)前操作的返回結(jié)果集

  • Cost(cpu): Oracle計(jì)算出來的一個(gè)數(shù)值(代價(jià)),用于說明SQL執(zhí)行的代價(jià)

  • Time列: Oracle估算當(dāng)前操作的時(shí)間。


還有些重要信息。比如

Predicate Information (identified by operation id): --------------------------------------------------- 4 - access("T1"."ID"="T2"."ID")

這一段是來說明謂詞信息和數(shù)據(jù)獲取的方式,它的意思在ID=4的那一列,通過訪問索引尋找數(shù)據(jù),而不是訪問原表數(shù)據(jù)。

還有個(gè)常見的方式 filter ,我們這里來解釋下access和filter的區(qū)別

如果執(zhí)行計(jì)劃顯示access, 就表示這個(gè)謂詞條件的值將會(huì)影響數(shù)據(jù)的訪問路徑(全表還是索引,這里是索引)

如果執(zhí)行計(jì)劃顯示filter,表示謂詞條件的值并不會(huì)影響數(shù)據(jù)的訪問路徑,只起到過濾的作用。


執(zhí)行計(jì)劃的最后一步是

這一步提示用戶CBO當(dāng)前使用的技術(shù),需要用戶咋分析執(zhí)行計(jì)劃時(shí)考慮到這些因素,比如現(xiàn)在提示這些信息時(shí),當(dāng)前表使用了動(dòng)態(tài)采樣,通過這個(gè)提示,我們就知道這個(gè)表可能沒有做過分析。

舉例:

Connected to Oracle Database 11g Enterprise Edition Release 11.2.0.4.0 Connected as xxx@xgjSQL> create table t(x int);Table createdSQL> begin for i in 1 .. 1000 loop insert into t values (i); end loop; commit; end; /SQL> select a.SQL_ID ,a.CHILD_NUMBER from v$sql a where a.SQL_TEXT like 'select * from t';SQL_ID CHILD_NUMBER ------------- ------------ 89km4qj1thh13 0 SQL> select * from table(dbms_xplan.display_cursor('89km4qj1thh13',0));PLAN_TABLE_OUTPUT -------------------------------------------------------------------------------- SQL_ID 89km4qj1thh13, child number 0 ------------------------------------- select * from t Plan hash value: 1601196873 -------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | -------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | | | 2 (100)| | | 1 | TABLE ACCESS FULL| T | 1 | 13 | 2 (0)| 00:00:01 | -------------------------------------------------------------------------- Note ----- - dynamic sampling used for this statement (level=2)17 rows selectedSQL>

我們手工執(zhí)行進(jìn)行表分析后,重新查看下

##表分析 SQL> exec dbms_stats.gather_table_stats(user,'t');PL/SQL procedure successfully completed##因?yàn)槭菑?span id="ozvdkddzhkzd" class="hljs-title">shared_pool中加載數(shù)據(jù),如果不清空,會(huì)影響執(zhí)行計(jì)劃 SQL> alter system flush shared_pool;System altered SQL>select * from t; .... SQL>select a.SQL_ID ,a.CHILD_NUMBER,a.SQL_TEXT from v$sql a where a.SQL_TEXT like 'select * from t%'; .... SQL> select * from table(dbms_xplan.display_cursor('ckzaraqcmkr2f',0));PLAN_TABLE_OUTPUT -------------------------------------------------------------------------------- SQL_ID ckzaraqcmkr2f, child number 0 ------------------------------------- select * from t Plan hash value: 1601196873 -------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | -------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | | | 3 (100)| | | 1 | TABLE ACCESS FULL| T | 1000 | 4000 | 3 (0)| 00:00:01 | --------------------------------------------------------------------------13 rows selectedSQL>

執(zhí)行計(jì)劃中已經(jīng)沒有動(dòng)態(tài)采樣的提示信息了。


第一次執(zhí)行SQL,CBO發(fā)現(xiàn)表沒有做表分析,于是使用動(dòng)態(tài)采樣的方式獲取數(shù)據(jù)信息。

SQL第二次執(zhí)行時(shí),CBO發(fā)現(xiàn)表已經(jīng)分析過了,于是就不會(huì)再使用動(dòng)態(tài)分析,而是直接使用分析數(shù)據(jù)。

這里會(huì)出現(xiàn)兩種情況:

  • 如果沒做表分析,CBO可以通過動(dòng)態(tài)采樣的方式來分析數(shù)據(jù),也可以獲取到正確的執(zhí)行計(jì)劃
  • 如果分析過,但是分析信息過舊,這時(shí)候CBO不會(huì)再使用動(dòng)態(tài)采樣,而是使用這些舊的分析數(shù)據(jù),有可能導(dǎo)致錯(cuò)誤的執(zhí)行信息

總結(jié)

以上我們闡述了執(zhí)行計(jì)劃輸出的全部?jī)?nèi)容。 當(dāng)我們?cè)诳磮?zhí)行計(jì)劃時(shí),不能只看執(zhí)行計(jì)劃的本身,還要看下面的謂詞和提示信息,這都非常有幫助。

總結(jié)

以上是生活随笔為你收集整理的Oracle优化05-执行计划的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。