天天看點

建立的索引解決了問題,結論真的對嗎?

今天這個案例相對比較簡單,算是對基本原理的一個加強了解吧。

客戶提供了一份其他公司做的優化報告,其中有個SQL确實是完成了優化,優化的結果也不錯,但是給出的優化理由卻是不準确的。

下面是報告對該SQL進行分析優化的描述:

SQL代碼如下:

SELECT COUNT(1)

FROM CB_PROBLEM A

LEFT OUTER JOIN CB_PROJECT B ON A.PRJ_SEQ = B.PRJ_SEQ

LEFT OUTER JOIN BS_LINE C ON A.PRB_LINE = C.LINE_SEQ

LEFT OUTER JOIN BP_FLOW D ON A.PRB_FLOW = D.FLOW_SEQ

LEFT OUTER JOIN BP_NODE E ON A.PRB_NODE = E.NODE_SEQ

LEFT OUTER JOIN BS_EFFECT F ON A.EFFECT_LVL = F.VALUE_SEQ

LEFT OUTER JOIN BS_FREQUENCY G ON A.FREQUENCY_LVL = G.VALUE_SEQ

LEFT OUTER JOIN BS_S EVERITY H ON A.SEVERITY_LVL = H.VALUE_SEQ

WHERE A.STATUS != 0

AND A.PRB_ORG IN

(

SELECT ORG_ID

FROM SP_ORG

START WITH ORG_ID IN (:1, ......, :18)

CONNECT BY PRIOR ORG_ID = SUP_ORG

)

AND A.PRJ_SEQ IN

(

SELECT PRJ_SEQ

FROM CB_PROJECT

WHERE 1 = 1 AND PRJ_NAME LIKE '%xxxxxxx%'

);

執行計劃如下(紅字是老虎劉補注):

建立的索引解決了問題,結論真的對嗎?

(老虎劉注:看到這個執行計劃有人可能會問,SQL語句中那麼多做left join的表,上面為什麼一個都沒看到?

這是因為最後要的結果是count(1),left join的表如果關聯字段都是唯一的(這個是根據執行計劃判斷出來的),優化器會很聰明的把這些表從執行計劃中消除,結果是等價的)。

其中主表CB_PROBLEM大小為312M,約44萬行資料。SQL平均執行時間為6分鐘。

CB_PROBLEM表目前索引情況:

INDEX_OWNER INDEX_NAME COLUMN_NAME COLUMN_POSITION

------------ --------------------------- -------------- ---------------

ICIMS01 IDX_CB_PROBLEM_PRJ_SEQ PRJ_SEQ 1

ICIMS01 IDX_PROBLEM_ID PRB_ID 1

ICIMS01 IDX_PROBLEM_SEQ PRB_SEQ 1

ICIMS01 IDX_PROBLEM_SEQ PRJ_SEQ 2

ICIMS01 SYS_C006663 PRB_SEQ 1

(老虎劉注: IDX_PROBLEM_SEQ索引首字段與SYS_C006663重複,SYS_C006663索引可以删掉 )

SQL中涉及的字段的選擇性:

COLUMN_NAME NUM_DISTINCT

------------------------ ------------

PRB_ORG 10319

PRJ_SEQ 75264

原報告由此得出的結論是:

可以看出,SQL中出現的字段PRJ_SEQ 已經在多個索引中出現,而且其選擇性也不錯, 為75264,之是以沒走上該字段的索引,是由于該字段對應的子查詢傳回結果過多,達到1萬多行,是以優化器沒有其上的索引。

SQL中還涉及另一個字段PRB_ORG,它的選擇性也不錯,為10319,并且其上沒有索引,經分析,建議在PRB_ORG和PRJ_SEQ上建立複合索引。

create index idx_test on ICIMS01.cb_problem(prb_org,prj_seq) online ;

我們先來看看增加索引後的SQL執行情況:

執行時間隻需要2.73秒,比原來的6分鐘有非常大的提高。執行計劃如下:

建立的索引解決了問題,結論真的對嗎?

下面我們來分析一下為什麼說這個索引建立的理由是錯誤的。

執行計劃中的步驟10由原來的全表掃描變成了索引掃描,這一步是建立索引的功勞。

執行計劃中,SP_ORG表作為初始驅動表,cb_problem表是作為nested loops的被驅動表,與驅動表的關聯字段隻有一個:PRB_ORG,雖然使用的是兩字段聯合索引,但實際上隻用到了第一個字段:PRB_ORG。

接下來cb_problem表又做為nested loops的驅動表,通過PRJ_SEQ字段驅動CB_PROJECT表時,使用的是CB_PROJECT表的關聯字段PRJ_SEQ字段上的索引,與此時的驅動表cb_problem表PRJ_SEQ字段上是否有索引無關。

也就是說,原結論建立兩個字段上的索引,其實隻需要一個字段就夠了,增加一個字段也不會提高索引在這個SQL中的選擇性(有可能在其他SQL同時使用兩個字段做謂詞條件時是高效的)。

總結:

在哪個表上建立索引,建立怎樣的索引才能使SQL執行效率最高,需要徹底搞清楚SQL執行計劃。有時問題解決了,可能還是沒有了解真正的原因。建立索引不是靠猜測,而是經過仔細分析後得出的結果。

兩表關聯做nested loops時,驅動表要求結果集(經過謂詞條件過濾後)要小,表的謂詞條件字段上一般要存在索引(不是關聯字段);被驅動表的關聯字段上要存在索引,這是基本常識。

比如下面這種OLTP系統常見的兩表關聯SQL:

select .... from t1,t2

where t1.object_id = t2.object_id and t1.object_name='T1';

執行計劃應該是t1表做驅動表,使用object_name字段上的索引,選出少量記錄,t2表做被驅動表,object_id字段(關聯字段)上要存在索引。

就這個sql本身而言,t1表的object_id字段上有沒有索引是沒有關系的。在某個客戶現場發現這樣的SQL,建立的是t1表object_id和object_name兩個字段的聯合索引,那就大錯特錯了。

後話:

優化後的執行計劃,根據顯示的估值行數,驅動表的行數還是比較高,前兩個表到底是做nested loops 還是做hash join好一些還不好說(沒有測試過),如果是hash join更好一些,那麼上面那個索引其實也沒有建立的必要了。