oracle性能優化


【雲和恩墨,提供7*24最專業的數據恢復(Oracle,MySQL,SQL server)服務,致力於為您的數據庫系統做最后一道安全防護!服務熱線:010-59007017-7030】數據恢復|數據庫運維|性能優化|安全保障|Oracle培訓|MySQL培訓

 

主題介紹:

Oracle執行計划的另類解讀:調皮的執行計划 | 誠實的執行計划 | 朴實的執行計划

 

說到執行計划,oracle的擁躉們自然而然會興奮起來。在ORACLE的世界里,執行計划有着其特殊的地位,如果我們將SQL性能優化看成一個生物,那某種程度上,執行計划就是DNA。在某搜索網站中,“oracle 執行計划”關鍵字的搜索結果與“oracle”關鍵字的搜索結果占比為1.7%。足可見執行計划在ORACLE中舉足輕重的地位:

image.png

而當我們輸入“oracle執行計划”時,推薦關鍵字排第一的就是“ORACLE執行計划怎么看”

image.png

一個標准的執行計划大致可以分為三個部分:訪問方式(表訪問、索引訪問等)、連接方式(NESTED LOOP、HASH JOIN等)及訪問順序(驅動表等)

image.png

我們對上述SQL稍加改動,再看執行計划:

image.png

什么情況?DEPT表不見了,執行計划居然“殘缺”了:

1、這是ORACLE的BUG嗎?

2、少了一張表,結果正確嗎?

3、ORACLE優化器如此大膽,其背后是誰在給他撐腰?

4、ORACLE憑什么擅作主張?

 

為了回答上述問題,我們就進入今天的第一個主題:殘缺的執行計划。

 

 

殘缺的執行計划

 

在展開之前,我們先做數據准備,分別創建兩張表EMP及DEPT,腳本如下:

 

CREATE TABLE DEPT(

        DEPTNO NUMBER(2), 

        DNAME VARCHAR2(14), 

        LOC VARCHAR2(13)); 

 

CREATE TABLE EMP(

        EMPNO NUMBER(4)CONSTRAINT PK_EMP PRIMARYKEY, 

        ENAME VARCHAR2(10), 

        JOB VARCHAR2(9), 

        MGR NUMBER(4), 

        HIREDATE DATE, 

        SAL NUMBER(7,2), 

        COMM NUMBER(7,2), 

        DEPTNO NUMBER(2) ); 

 

現在我們有如下一條語句:

SELECT COUNT(1)

  FROM EMP E

  LEFTJOIN DEPT D

    ON E.DEPTNO = D.DEPTNO

 

這條語句非常簡單,就是獲取EMP表與DEPT表內關聯后的數據量。在看具體的執行計划之前,我們解讀下在常規情況下,DB是如何處理這樣的數據的

1、分別讀取emp表和DEPT表的數據;

2、對EMP中的DEPTNO與DEPT表中的DEPTNO進行內關聯;

3、對內關聯后的數據進行匯總計算;

4、返回匯總計算結果。

也就是說會存在EMP與DEPT表的內關聯,因為SQL就是這樣寫的。那我們看下該語句的執行計划,如下:

image.png

ORACLE優化器果真是按照我們的預想制定了執行計划。

 

 

 

1唯一性字段對執行計划的影響

 

由於在模型分析時,我們發現DEPT表的DEPTNO字段是唯一的。於是我們需要通過如下語句為該字段創建主鍵:

ALTERTABLE DEPT ADDCONSTRAINT PK_DEPT PRIMARYKEY(DEPTNO);

 

我們再回過頭來看執行計划,會發生變化嗎?

image.png

如果此時的你還不能看出問題,那么我們就對比下DEPT表的主鍵創建先后執行計划的變化:

image.png

俗話說:不比不知道,一比嚇一跳。DEPT莫名其妙的被ORACLE優化器弄“丟”了。這不禁讓人懷疑:這樣的裁剪是否是不負責任的?也就是說,裁剪后的結果是否會因為裁剪而發生變化?在深入了解到LEFT JOIN的原理及模型結構后,你就會明白為何ORACLE優化器在DEPT表創建了基於DEPTNO字段的主鍵后,會做這樣的裁剪。

 

支持ORACLE做如此大膽裁剪的理由是:

1、 LEFTJOIN在沒有where條件過濾的時候,是不會減少結果數據量的;

2、 如果被關聯的字段是被關聯表的主鍵(或者唯一性字段),那么是不會使結果數據量增多的。

 

既然結果集的數據量不增加也不減少,那為何還要多訪問一個表,多做一次關聯呢?這就是ORACLE的精明之處:簡單的就是高效的。

 

接下來,我們繼續上面的實驗(當然是基於上面的模型基礎,即在DEPT表上創建了基於DEPTNO字段的主鍵)。這次,我們將LEFT JOIN改成INNER JOIN,看看執行計划是怎么樣的:

image.png

表結構和約束關系沒有發生變化,消失的DEPT又回來了。

神馬原因呢?LEFT JOIN是不會有數據過濾的作用的,但是INNER JOIN則有過濾的功用。

為了驗證,我們准備如下數據:

INSERTINTO dept(deptno,dname)VALUES(14,'財務');

INSERTINTO dept(deptno,dname)VALUES(31,'行政');

INSERTINTO EMP(EMPNO, ENAME, DEPTNO)VALUES('001','張三',14);

INSERTINTO EMP(EMPNO, ENAME, DEPTNO)VALUES('002','李四',31);

INSERTINTO EMP(EMPNO, ENAME, DEPTNO)VALUES('003','王五',21);

INSERTINTO EMP(EMPNO, ENAME, DEPTNO)VALUES('004','麻六',14);

現在來看看LEFT JOIN和INNER JOIN的不同結果:

image.png

image.png

也就是說,LEFT JOIN和INNER JOIN還是有差異的,那么在什么情況下才能在執行計划中將DEPT“槍斃”掉呢?

 

 

2主外鍵約束對執行計划的影響

 

我們對EMP和DEPT創建一個主外鍵約束(在創建主外鍵約束前,我需要刪除掉empno=’003’的記錄):

ALTERTABLE EMP ADDCONSTRAINT FK_DEPTNO FOREIGNKEY(DEPTNO)REFERENCES DEPT(DEPTNO);

 

看看效果如何:

image.png

這樣是不是已經非常明確了DEPT再度消失的原因了?因為創建了主外鍵,也就是等於說EMP所有的DEPTNO必須要存在DEPT表中,既然有這樣的約束,那自然就不需要多此一舉的關聯DEPT表了。

 

 

3字段屬性對執行計划的影響

 

現在我們往EMP表里面再添加一條數據:

INSERTINTO EMP (EMPNO, ENAME, DEPTNO)VALUES('005','趙七',NULL);

再看看INNER JOIN的結果:

image.png

結果只有3條數據,明顯剛才新增的數據是被過濾掉了,因為他的DEPTNO為null,其null並沒有存在於dept表中。

而在執行計划里面,是沒有DEPT表的:

image.png

也就是說該SQL就應該等價於如下SQL:

SELECT E.EMPNO, E.ENAME

 FROM EMP E

我們再看結果:

image.png

 

不對呀,說好的等價呢?難道是執行計划出了問題?還是我們對執行計划的理解錯了?也或許是執行計划對我們隱藏了什么?

以上,我們都是在ORACLE的第三方開發工具PL SQL DEVELOPER里面查看的執行計划。現在我們換種方式,在SQL PLUS里面通過explain plan這種最原始的方式來查看執行計划,如下:

image.png

原來,在這個執行計划的內容中,明顯的多出了“Predicate Information”,而在這部分信息里面,filter是:E.DEPTNO IS NOT NULL。

 

好吧,我們先把這個謂詞放進SQL中,看看效果:

image.png

果然,我們發現,增加了這個謂詞后,兩個SQL又等價了。此時,我們會想:天哪,如果再遇到其他場景,會不會又不等價了?

 

在關聯條件存在主外鍵關系約束的前提下,如下兩個SQL是等價的:

image.png

不管你信不信,反正我信了

 

而此時,我們來看看EMP.DEPTNO的字段屬性:

image.png

我們發現其Nullable屬性為true,即可為null值。而如果我們將該屬性值修改為false呢?

DELETEFROM emp WHERE empno ='005';

COMMIT;

ALTERTABLE EMP MODIFY DEPTNO NOTNULL;

再看執行計划:

image.png

我們發現原來的“Predicate Information”不見了,也就沒有了E.DEPTNO IS NOT NULL的謂詞約束。

 

 

4程序員與ORACLE的較量

 

在上面,我們在極力“宣傳”着oracle是多么多么的智能化,而事實上,她的智能程度也是存在局限性的,比如她對SQL語句的取舍絕對的依賴於物理模型結構及約束,而一旦這種物理模型結構及約束不存在,那么ORACLE這位“巧婦”顯然也只能“難為無米之炊”了。即便我們在SQL中進行了(唯一性)約束,ORACLE也會選擇視而不見,比如如下SQL:

SELECTCOUNT(1)

FROM EMP E

LEFT JOIN (SELECTDISTINCT DEPTNO FROM DEPT) D

ON E.DEPTNO =D.DEPTNO;

 

按照我們在上面的理解,由於在子查詢D中,已經對DEPTNO進行了distinct處理,也就意味着在子查詢D中,DEPTNO絕對是唯一性的,即子查詢D對整個SQL返回的結果是沒有任何影響的,該SQL完全等價於如下SQL:

SELECT COUNT(1) FROM EMP E

 

而事實上呢,我們看看ORACLE的執行計划:

image.png

 

這一次很讓我們意外,ORACLE居然沒有識別出子查詢D的作用。由此看來,在某些時候,尤其是在錯綜復雜的業務邏輯面前,oracle往往束手無措,遠沒有程序員聰明,所以在ORACLE這位巨無霸面前,我們也大可不必妄自菲薄。

 

 

5總結

 

 

至此,我們可以為第一個主題做出如下總結:

1、ORACLE優化器為達性能之目的,會不擇手段的簡化Operation;

2、ORACLE優化器的手段之一就是充分利用數據庫約束,包括但不局限於:唯一性約束、主外鍵參照性約束、Nullable約束;

3、在約束條件內,ORACLE會簡化SQL,在Operation時不再重復約束;

4、因此,在日常模型設計時,應盡可能的建立約束,最大程度上減少重復約束帶來的“非戰斗性減員”,從而提升SQL性能

 

 

完整的執行計划

 

在上一節的最后示例中,為了更全面闡述問題,我們“拋棄”了在PL SQL DEVELOPER通過F5得到的執行計划,轉而選擇了最原始最古老的explain。因為我們發現:

image.png

這幾列還不足夠支撐我們了解ORACLE優化器的意圖,或者說還不夠讓我們拼湊出ORACLE優化器對SQL改寫后的全貌。至少我們還需要謂詞(Predicate)。所以,一個完整的執行計划除了:訪問方式(表訪問、索引訪問等)、連接方式(NESTED LOOP、HASH JOIN等)及訪問順序外,還應包括謂詞(Predicate),通過結合謂詞,我們更能還原ORACLE優化器對SQL做了哪些改動?

 

為了直觀期間,我們還是繼續在PL SQL DEVELOPER中演示,只是執行計划的正確打開方式是這樣的:

image.png

那么我們能從謂詞中發現什么呢?

 

我們都知道,在表的統計信息采集及時的場景下,如果某個索引字段存在條件過濾,而執行計划中沒有通過索引訪問,而是table access full。那么原因無非就是:該過濾條件值的數據量太大(比如超過全表數據量的20%),或者是SQL的寫法不當(該字段上應用了函數、表達式等)。

 

其實,除了上述兩種場景外,還有一種場景也會導致table access full。我們先來看一個非常簡單的案例,我們在EMP.DEPTNO上創建一個索引,因為經常會遇到查詢某個特定部門的員工信息。

CREATEINDEX EMP_I1 ON EMP(DEPTNO);

因為在DEPT表中,DEPTNO的數據類型為NUMBER(2),在查deptno為14的員工信息時,我們會習慣性的寫成:

SELECT*FROM emp WHERE deptno =14;

 

我們的如意算盤是通過索引EMP_I1來訪問EMP表。而事實上,從執行計划看,卻是table access full的訪問方式:

image.png

 

盡管deptno=14的數據量為0,並且也沒有在deptno上有任何的函數或者表達式。那么問題出在哪里呢?

 

我再來看看謂詞:

image.png

很明顯,在實際的執行過程中,DEPTNO是被TO_NUMBER函數包了一層,自然就走不了索引。那么是什么讓ORACLE如此“昏庸”,以致“無事生非”的添加一個函數呢?

 

我們再看看EMP.DEPTNO的數據類型:

image.png

原來,EMP.DEPTNO的數據類型並沒有同DEPT.DEPTNO保持一致,被設計成了VARCHAR2。因此要想走索引,就有三種辦法:將DEPTNO的數據類型修改為NUMBER(2)、創建TO_NUMBER(DEPTNO)的函數索引、將過濾條件有之前的DEPTNO=14修改成DEPTNO=’14’。

 

我們就看下第三種方案的執行計划:

image.png

這才是我們想要的執行計划,卻不是我們想要的表模型。這三種方案孰優孰劣不在本次分享主題范圍內,如有機會,再行討論。

 

沒錯,這就是隱式強行轉換的風險,而所有的字段隱式轉換在執行計划中都會被“曝光”

隱式轉換都是“無意為之”,有兩種場景:其一是對過濾字段的數據類型“想當然”的認為;其二是對過濾值類型的錯誤判斷。剛才的案例屬於第一種,那么第二種又是怎么回事呢?

 

以下是一個真實的案例:

系統中存在一個日志表,數據量非常大,我們對日志表按照日志時間(log_date)做了分區。在頁面,要求強制按照log_date過濾,以命中分區而提高效率。但是分區+強制過濾並沒有收到預期的性能效果,但是將同樣的查詢條件直接在DB中執行卻非常快。通過對比執行計划發現,通過頁面調用執行時,並沒有命中分區,而在訪問謂詞中,log_date字段過濾時,多出了函數INTERNAL_FUNCTION。也就是將log_date字段隱式強制轉換成了timestamp。而導致這種問題的原因是JAVA數據類型與ORACLE數據類型之間的轉換出現了問題。最后通過JAVA傳STRING到ORACLE,然后在SQL中將變量值TO_DATE成DATE類型解決。

 

我們也可以簡單模擬下。比如我們在EMP中創建基於HIREDATE的索引:

CREATEINDEX EMP_I2 ON EMP(HIREDATE);

我們現在要查找今年以來入職的員工信息,SQL如下:

SELECT*FROM EMP WHERE HIREDATE >SYSDATE

其執行計划如下:

image.png

而如果我們將date’2016-01-01’轉換成timestamp,則執行計划如下:

 

在這個案例中,如果不查看謂詞,就很難找到性能的根源。

 

 

朴實的執行計划

 

我們繼續執行計划中的“謂詞”,看看還能給我們哪些意外之喜?

在上個章節中,我們注意到,在查詢今年入職的員工信息是,我們用了DATE’2016-01-01’。這種寫法很少見諸於正式書籍中,因為這是非標准寫法。那么將VARCHAR2轉換成DATE的標准寫法是什么呢?

 

執行計划會告訴你:

image.png

 

原來DATE’2016-01-01’被轉換成了TO_DATE(‘2016-01-01’, ‘SYYYY-MM-DDHH24:MI:SS’),這樣是為什么DATE只能支持YYYY-MM-DD格式的字符串的原因:

image.png

可見,ORACLE優化器會將SQL中一些非標准的寫法轉換成標准的朴實的寫法。有的時候,最朴實的寫法,最容易讓人理解。

比如,當你拿到如下SQL時:

SELECT ENAME, SAL

  FROM EMP

 WHERE SAL >SOME(SELECT SAL FROM EMP WHERE DEPTNO =10);

你會不會很懵菜?會不會去查資料,研究SOME的作用和用法?或許大半天后,你仍然被SOME\ANY\ALL弄得雲山霧罩的。

 

現在,我們試着從執行計划去探究>SOME的含義。

image.png

 

我們將子查詢替換成具體的LIST(100,200,300),發現在執行計划中,謂詞變成了SAL > 100,意思就是大於最小值。換言之,原來的SQL就是要查詢大於DEPTNO=10部門最低工資的員工信息。

 

 來源:http://www.enmotech.com/web/detail/1/317/2.html


免責聲明!

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



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