SQL優化的中心思想有兩點:
- 少做甚至不做。
少做事情,甚至對不必要的事情干脆不做,自然就能使SQL提高效率。
通過使用索引來減少數據掃描就是少做事思想的最典型的情形。
- 集中資源做一件事情。
同樣的工作量,相比一個人做,大家一起做自然就快了。並行操作就屬於這種情形。
索引就是爲了快速查找表中記錄所創建的一種特殊數據結構。索引塊中包含字段的值和指向對應行的地址——rowid。
下面通過示例來介紹一下爲什麼索引會提升SQL的執行效率:
1、當執行select * from t1 where id = 10000 這樣的語句時,如果id字段上創建了索引,並且id 字段中的唯一值較多時,SQL優化器會判斷選擇走索引。
2、走索引的話,會代入10000 去索引中匹配,經過根節點、枝幹節點,找到葉子節點中對應的id字段值爲10000的索引項,讀取索引項中行記錄的地址信息,即rowid。
3、接下來通過rowid直接讀取表中的行記錄,如果where 後邊還有其他條件,將會對行記錄進行校驗,並返回最終符合條件的行記錄。
如果id 字段上沒有索引,那麼SQL將如何執行呢?
如果id 字段上沒有索引,那麼SQL只能選擇全表掃描,對錶中的所有數據進行過濾,並返回最終符合條件的行記錄。 隨着表中數據量的增長,全表掃描所消耗的資源將會越來越多,SQL執行時間會越來越長。
由索引結構圖中,我們可以得出索引的三大特徵,並藉助這三大特徵對SQL進行優化。
- 高度較低(可快速定位)
通過索引,小表查詢與大表查詢性能差異很小。
- 存儲列值(可優化聚合)
count, sum/avg, max/min, 多列索引避免回表
- 有序(可優化排序)
order by, max/min, union, distinct
怎麼理解索引呢? 想想新華字典中的拼音索引或部首索引就能理解了。
部首索引中會主要記錄了兩類信息,即部首和部首出現的頁數。同樣的,數據庫中索引也會記錄被索引字段的值和該值在表中出現的位置。
當我們查找一個不認識的字的時候,我們使用部首索引就可以快速查找到我們要找的文字,而不用將整個字典從頭翻到尾一頁一頁的找。
新華字典中文字部分可以理解成表,爲了快速查找到要找的文字,創建了部首索引。 我們要快速的從表中找到想要的記錄,也同樣的需要創建索引。
因爲在索引中,數據是有序的,使用索引可以快速的定位到我們要查找的內容。然後通過索引中的rowid(可以理解成數據存儲的物理位置), 也就可以直接去表中讀取數據了。
創建索引時,一般會在where 條件後的字段上創建索引, 這樣就可以通過索引快速查找到相應的記錄了。
但是,假如通過查詢條件查找到的記錄較多,那麼,索引效率就不會高。 對於這種情況,就不應該創建索引了。
與索引查找相對應的是全表掃描,下面就將全表掃描和索引查找的概念簡單介紹一下:
全表掃描:全表掃描是數據庫服務器用來搜尋表的每一條記錄(表中的行)的過程,直到所有符合給定條件的記錄返回爲止。因爲只有把全表所有記錄從頭到尾找一個遍,才能確定滿足查詢條件的記錄全被找到。
全表掃描的一般使用場景:
1、表上沒有索引。
2、由於滿足查詢條件的記錄較多,導致使用索引效率也不會高, 這時也會選擇全表掃描。
3、對錶執行全記錄操作,例如沒有條件限制的查詢表中所有記錄,沒有條件限制的排序或聚合(group by, sum,count) 操作等。
以上場景中,2 和 3 是無法避免的, 也是不應該避免的。 因爲在這種情況下,全表掃描相比索引查詢性能會好。
索引掃描:在只需要查詢表中少量記錄的時候,使用索引掃描效率會高。 因爲索引結構的特殊性,通過索引可以快速定位到要查找的記錄,而不用將整個表從頭找到尾。
索引的真正意義:索引是優化器在制定執行計劃時,爲了尋找最優化的路徑而使用的戰略要素。假如表上沒有索引,那優化器就只能選擇全表掃描的執行計劃。
索引雖然可以幫着我們快速查找記錄,但是,索引也是有成本的。
當表中插入、刪除記錄和更新索引字段的記錄時,數據庫就會自動維護到索引中,這部分開銷是不容忽視的。 隨着表上索引個數的增多,索引維護開銷對性能的影響會越來越明顯。 因此,索引不能盲目的創建,只有切實發揮有益作用的索引才值得創建。
正因爲如此,我們才強調,創建索引要從全局綜合性考慮絕大部分SQL的數據訪問需求,創建戰略性索引,用盡量少的索引來滿足絕大部分SQL的性能需求。
單列索引創建原則:
- 查詢條件中的經常使用列
- 列的離散度要高
- 通過索引只查詢很少一部分數據,小於5%(這個只是一個大概值)。
- 查詢條件不能是 <>。
複合索引創建原則:(重要程度依據序號所示)
1、是否經常被使用?
2、是否經常爲列使用“=”比較查詢條件?
3、哪個列具有更好的離散度?
4、經常按照何種順序進行排序?
5、何種列將作爲附加性列被添加?
以上是創建索引的原則,也可以說是創建索引的依據。開發人員應當多多在工作中練習體會。
- 寫好SQL,不犯低級錯誤。
SQL書寫中常見的低級錯誤有:
- 對列進行運算(這裏的意思是能避免就避免)
- 對列使用函數(這裏的意思是能避免就避免)
- 數據類型不一致導致列發生隱式轉化
- 查詢列表中直接使用 * 查詢所有字段,而間接包含了不需要的字段
- 進行不必要的排序
- union 可用 union all 代替
- 使用不必要的distinct
如果我們能避免以上低級錯誤,就算是寫好了SQL。
注意,以上並沒有提到多表關聯查詢時, 表的順序, 以及查詢條件where後的條件的前後順序。 在oracle現有主流版本中,表順序、where條件順序對SQL執行沒有任何影響,oracle會自動分析判斷使用最高效的執行路徑。
除了一些低級錯誤之外,下面對SQL 書寫方面普遍存在的訛傳進行一下闢謠:
- 表的連接順序、表的條件順序
這種說法在oracle9i之後就過時了,現在已經很難找到oracle8i的數據庫了,現在已經不在區分書寫順序了。
- Count(*) 與 count(列) 的性能比較
首先,要注意以上兩種寫法本身就不等價。 count(*) 是查詢表中的記錄數,空值也會被統計到;而count(列) 是統計該列值的個數,空值是不被統計的。
其次,性能比較的話,也會因爲是否會使用到索引而不同。 如果表上有主鍵,count(*)、count(1), count(主鍵列) 的性能是一樣的,其執行計劃都是選擇主鍵索引進行快速索引全掃描。Count(其他列) 的話,如果對應列上沒有索引,列序越靠後,則用時越長。如果有索引,則將不會有什麼差異。
- In 與 exists 性能之爭
在現有oracle主流版本中,已經沒有什麼區別了。
- Not in 與 not exists 之爭
以上兩者性能上並沒有明顯差異。 但是,鑑於not in ( ) 中存在空值時查詢結果爲空,推薦使用not exists。
給大家闢謠之後,以後寫SQL就不用再顧忌那麼多了,是不是輕鬆許多? 那是肯定的!
我們創建索引時,不但要着眼於某一條SQL,更要着眼於訪問表的絕大部分SQL,根據SQL的佔比,執行頻次來進行綜合的分析考量。
索引是有成本的,索引不是越多越好。
正確的創建索引的理念力,是力求用最少量的索引,滿足絕大部分SQL的性能要求。
大家試想一下,在表中數據大幅增長的情況下,如何保證應用性能仍然可控? 即保證SQL性能不會明顯下降。
如果表上沒有索引,也就是對數據的訪問是全表掃描的方式,那麼,隨着表中數據量的增長,性能會持續下降,應用的執行時間會增長,用戶體驗會越來越不好。
可以預見的是,這樣的應用程序的生命週期是非常短暫的。
如果SQL使用到索引,那情形就不一樣了。 因爲對於索引訪問方式,即使表中數據增長十倍的情況下,SQL性能也不會有明顯下降。這就是索引的優勢!!
開發人員將SQL寫好後,還需要負責創建適當的索引嗎? 當然!
因爲就算你將SQL寫好了,如果缺少必要的索引,oracle優化器也不會創建出高效的執行計劃。你讓oracle將事情做好,最起碼的優化手段得提供給他吧?
如何理解訪問方式呢?有以下幾點:
- 通過哪些列對數據進行訪問?
- 各種訪問方式的頻率如何?
- 訪問列的離散程度如何?
理解之後做什麼呢?
- 在應用開發階段,就應該創建好相關索引。
- 綜合考量對錶的訪問方式,創建戰略性索引。
由於開發人員對錶中數據的多少、數據的分佈、數據的訪問方式是最瞭解的,因此,我們可以說,開發人員對SQL進行性能優化,比其他人員具有不可比擬的先天優勢!
- 可發現SQL中存在的問題,能進行簡單調優。
性能優化一個漸進的過程,這種能力,需要在掌握各種優化技能的基礎上,多多練習體會。對開發人員來說,有優化意識,能發現SQL的性能問題,進行簡單調優就相當可以了。
在我們看來,意識和技能正是優化的兩大法寶。
意識就是善於思考,善於發現,多問幾個能不能。例如怎樣才能少做事? 哪些操作是不必要的? 應用性能還能不能快一點? 服務器能不能少多一點? 用戶的真實需求是什麼?
很多時候,優化更多是意識方面的,無需任何優化技能就能實現的。
技能指掌握性能優化的基本知識,這些是我們優化時的手段和工具,包括索引、表連接、分區表和數據庫原理等。只有掌握了這些技能,在遇到問題時,我們才能使用這些手段去解決問題。技能是性能優化時不可或缺的。
這三類集合操作都會去除重複值,會進行排序操作。 對於union,優先考慮是否可用union all代替。
除了性能考慮之外,對於我們開發人員來說,首先要對 union/union all/intersect/minus 集合運算結果有個清楚的認識。
以下是對 union/intersect/minus 集合操作的演示:
SQL> select * from t1 order by id;
ID
----------
1
1
2
2
3
3
4
4
5
5
6
7
8
9
14 rows selected
SQL> select * from t2 order by id;
ID
----------
1
2
3
4
上面,是查詢中涉及到的兩張表t1 和 t2,下面將對兩表進行集合運算。
SQL> select * from t1
2 union
3 select * from t2;
ID
----------
1
2
3
4
5
6
7
8
9
9 rows selected
SQL> select * from t1
2 minus
3 select * from t2;
ID
----------
5
6
7
8
9
SQL> select * from t1
2 intersect
3 select * from t2;
ID
----------
1
2
3
4
從執行結果中,我們注意到union,minus, intersect 集合運算都進行了去除重複值的運算。
SQL> select * from t1
2 union all
3 select * from t2;
ID
----------
1
2
3
4
5
6
7
8
9
1
2
3
4
5
1
2
3
4
18 rows selected
SQL>
以上執行結果, 是否與你預期的結果一樣呢?
儘可能將where條件放到SQL最裏層,以便在SQL執行之初,就將不符合的數據過濾掉。
SELECT COUNT(*)
FROM (SELECT AGENTCODE,
IDNO,
GREATEST(OFFWORKDATE, NVL(REWORKDATE, DATE '2006-01-01')) AS INDATE,
DECODE(AGENTSTATE,
'0',
LEAD(OFFWORKDATE, 1, ADD_MONTHS(SYSDATE, 12))
OVER(PARTITION BY AGENTCODE ORDER BY DEPARTTIMES),
LEAD(OFFWORKDATE, 1, DATE '2006-01-01')
OVER(PARTITION BY AGENTCODE ORDER BY DEPARTTIMES)) AS OUTDATE
FROM (SELECT A.AGENTCODE,
IDNO,
A.DEPARTTIMES,
A.OFFWORKDATE,
A.REWORKDATE,
DECODE(B.AGENTSTATE, '01', '0', '02', '0', '1') AS AGENTSTATE
FROM LADIMISSION A, LAAGENT B
WHERE A.AGENTCODE = B.AGENTCODE
AND OFFWORKDATE IS NOT NULL
AND B.BRANCHTYPE = '1'
UNION ALL
SELECT AGENTCODE,
IDNO,
0,
JOINDATE,
NULL,
DECODE(AGENTSTATE, '01', '0', '02', '0', '1') AS AGENTSTATE
FROM LAAGENT
WHERE BRANCHTYPE = '1'))
WHERE IDNO = :B3
AND IDNO <> :B2
AND :B1 BETWEEN INDATE AND OUTDATE - 1
分析SQL語句可以發現, WHERE IDNO = :B3 這個條件部分可以直接從SQL的外層放到最裏層去。
排序操作將嚴重影響語句執行效率。排序是資源開銷最大的一類操作,所以要堅決去掉沒有必要的排序開銷,或者借用索引來避免排序。
增加排序,意味着需要做怎樣的操作?
首先,對全表數據進行掃描。
其次,對相關字段應用排序算法,計算出排序的結果。 這個過程中,CPU、內存、磁盤三種資源開銷都會不小。
涉及排序的操作:
A、創建索引
B、涉及到索引維護的並行插入
C、order by 或者group by (儘可能對索引字段排序)
D、Distinct
E、union/intersect/minus
F、sort-merge join
G、analyze命令(儘可能使用estimate而不是compute)
示例SQL:
select *
from (select a.missionprop1 理賠編號,
getCaseState(a.missionprop1) 案件狀態,
a.missionprop3 出險人客戶號,
a.missionprop4 出險人姓名,
(select codename
from ldcode
where codetype = 'sex'
and code = a.missionprop5),
to_date(a.missionprop6, 'yyyy-mm-dd') 出險日期,
(select rptdate from llreport where rptno = a.missionprop1),
(select shortname from ldcom where comcode = b.mngcom) 報案機構,
(select username from lduser where usercode = b.operator) 報案記錄人員,
a.missionid,
a.submissionid,
a.activityid,
a.missionprop2,
(select case count(1)
when 0 then
''
else
'回退案件'
end
from LLCaseBack
where rgtno = a.missionprop1) 標誌,
(case (select isEnpAdded
from LLRegister
where rgtno = (select b.rgtobjno
from llregister b, llcase c
where caseno = a.missionprop1
and c.rgtno = b.rgtno))
when '1' then
'是'
else
'否'
end),
(select d.username
from lduser d
where a.defaultOperator = d.usercode) 當前操作人員,
(select shortname
from ldcom
where comcode = (select ll.mngcom
from llregister ll
where ll.rgtno = a.missionprop1)) 立案機構,
(select username
from lduser
where usercode =
(select ll.operator
from llregister ll
where ll.rgtno = a.missionprop1)),
a.defaultOperator,
'' a3,
'' a4,
a.makedate,
a.maketime,
(case
when isWebCase(a.missionprop1, 1) = '1' then
'是'
else
'否'
end),
(case
when isWebCase(a.missionprop1, 0) = '1' then
'是'
else
'否'
end)
from lwmission a, llreport b
where 1 = 1
and a.missionprop1 = b.rptno
and a.activityid = '0000005002'
and a.processid = '0000000005'
and a.DefaultOperator is null
order by isWebCase(a.missionprop1, 0) desc,
getVIPGradeForClaim(missionprop3) desc,
a.makedate,
a.maketime)
where rownum <= 300
SQL優化分析:
想辦法規避掉排序操作,尤其這裏先進行函數計算後再進行排序,非常消耗資源。
這裏暫且採用了創建函數索引的方式來避免排序:
create index ix_lwmission_actid_prcid_dop on
lwmission(activityid,processid,defaultoperator,isWebCase(missionprop1,0)) tablespace lisindex;
不過,函數索引的創建有一些風險,我們應儘量從業務角度考慮避免排序操作。
使用等於操作時,是可以藉助索引來提升性能的,使用like時,儘量避免在左邊使用%,這樣還可以使用上索引。
在應用程序功能設計時,除了考慮設計完美的功能外,也應該考慮到程序的性能和執行效率。在功能與效率之間,需要一個平衡。
from LCGrpCont a, LAAgent b, LCCont c
where a.AgentCode = b.AgentCode
AND 1 = 1
and a.AppFlag = '1'
and a.GrpContNo = c.GrpContNo
and c.cardflag <> '4'
and a.ManageCom like '8661%'
and a.GrpName like '%寶塔保安服務有限公司%'
and specflag = '0'
and a.ManageCom like '8661%'
and a.cinvalidate >= '2015-01-19'
Order by CValiDate,
grpContNo,
a.ManageCom,
a.GrpContNo,
a.PrtNo,
a.SignDate,
a.CValiDate
分析:
以上SQL中,可以考慮將like 改爲等於操作。
where 條件中等號兩邊數據類型要一致;表連接時,等號兩邊字段數據類型也要一致。
select max(to_number(SUBJECT_SORT)) as count
from FOCUS.OBT_OBJECT
where PSP_ID = 201403101289
and PSP_VERSION_NUM = 4.4
以上PSP_ID字段爲字符型,等號兩邊類型不一致會導致隱式類型轉換的發生。這裏的隱式轉化是字符型向數值型的隱式轉換,所以會導致PSP_ID 上的索引失效,進而會導致全表掃描。
select *
from LISPROD.LJAPAYPERSON
where POLNO = 2010000337166021
and PAYNO = 2014001945828031
and DUTYCODE = 'P28001'
and PAYPLANCODE = 'P28101'
and PAYTYPE = 'ZC'
以上POLNO字段爲字符型, 這會引起隱式轉換。
這個看具體情況,一般情況下不需要。
當semi join 與 or 連用的時候,如果因爲OR 導致偶Filter操作符,就需要改寫SQL了。
SELECT *
FROM LJTempFee
WHERE ((OtherNoType = '0' AND
OtherNo IN
(SELECT PolNo FROM LCPol WHERE PrtNo = '10088800971797')) OR
(OtherNoType = '4' AND OtherNo = '10088800971797'));
可改寫成:
SELECT *
FROM LJTempFee
WHERE OtherNoType = '0' AND OtherNo IN (SELECT PolNo FROM LCPol WHERE PrtNo = '10088800971797')
union all
select * from LJTempFee
where OtherNoType = '4' AND OtherNo = '10088800971797';
注意:
這裏如果使用union all,將可能存在改寫不等價的風險。
因爲如果有記錄同時滿足OR前後條件的話,使用OR邏輯運算符滿足前後兩個條件的記錄只會出現一次。
但是,如果改成union all,將會導致記錄出現兩次。
當然,改成union的話,爲了保證等價,表上必須有主鍵。否則,如果表上沒有主鍵的話,也一樣沒法保證改寫是等價的,因爲union會對記錄去重。
通過指定字段,可以減少不必要字段的查詢,減少不必要的資源開銷,提高執行效率。尤其是表中包含LOB字段類型時效率提升更明顯。
select * from LPEdorItem where EdorType='LF' and PolNo='000000';
分析:
該語句需要查詢表中所有字段嗎? 儘量不要 *,應使用確定的字段名代替星號。
在多表連接的語句中, 表要使用別名,各個字段都要帶上表別名。這個也屬於開發規範方面,當分析SQL時,對字段屬於哪個表就很清晰。
對於日期類型字段的賦值或比較時, 推薦使用to_date函數構造日期。
不要使用 date '2015-01-15' 或 直接使用字符串的方式,因爲這兩種寫法將會嚴重依賴數據庫的環境變量設置。
儘量不要對字段使用函數或進行運算,對字段進行計算將無法使用該字段上的索引。
遇到這種情況,一般可通過等價改寫來避免,實在無法改寫的可創建函數索引。
當然,對語句進行等價改寫時,需要高度注意改寫的等價性。
示例:
where substr(m.polmngcom, 1, 6) = '863401'
可改寫爲:
where m.polmngcom like '863401%';
where addtime + 1 > sysdate
可改寫爲:
where addtime > sysdate - 1;
where trunc(addtime) = trunc(to_date('2015-05-01','yyyy-mm-dd'))
應等價改寫爲:
where addtime <=to_date('2015-05-02','yyyy-mm-dd')
and addtime > to_date('2015-05-01','yyyy-mm-dd')
WHERE "PAYPLANCODE" = 'P69101'
AND "DUTYCODE" = 'P69001'
AND "PAYTYPE" = 'ZC'
AND TO_NUMBER("POLNO") = 2012001535421021
AND TO_NUMBER("PAYNO") = 2015000344156031
以上可等價改寫爲:POLNO = '2012001535421021' and PAYNO = '2012001535421021'
delete from fin_document_sum s
where trunc(s.document_date) >= date '2015-04-25'
and trunc(s.document_date) <= date '2015-04-28'
可等價改寫爲:
delete from fin_document_sum s
where s.document_date >= to_date('2015-04-25', 'yyyy-MM-dd')
and s.document_date < to_date('2015-04-29', 'yyyy-MM-dd')
爲什麼要避免對字段進行運算或函數操作呢?
如果對字段進行函數運算,那麼將會對錶中每條記錄的對應字段進行運算,其計算量將是非常大的。
以下面SQL示例說明:
delete from fin_document_sum s
where trunc(s.document_date) >= date '2015-04-25'
and trunc(s.document_date) <= date '2015-04-28'
以上SQL將如何執行呢?
首先,對fin_document_sum 表的每條記錄中的 document_date 字段進行trunc操作,
然後,將trunc(s.document_date) 的值與查詢條件進行比較,如果滿足,則將記錄刪掉。
就算等價改寫後不能使用創建在document_date字段上的索引,也一樣會有性能上的提升。
因爲trunc(s.document_date) >= date '2015-04-25',會先對document_date 執行trunc操作後,再執行比較操作。
而s.document_date >= to_date('2015-04-25', 'yyyy-MM-dd') 則不需要執行trunc操作,直接進行比較操作即可。
性能優化就是儘可能的減少不必要的操作!!!!
update laprecontinue a
set managecom =
(select managecom
from laagent
where branchtype = tBranchType
and agentcode = a.agentcode),
branchcode =
(select branchcode
from laagent
where branchtype = tBranchType
and agentcode = a.agentcode),
branchattr =
(select branchattr
from laagent b, labranchgroup c
where b.branchcode = c.agentgroup
and b.agentcode = a.agentcode
and c.branchtype = tBranchType
and b.branchtype = tBranchType)
where not exists (select 1
from laagent d, labranchgroup e
where d.branchtype = tBranchType
and agentcode = a.agentcode
and d.branchcode = e.agentgroup
and e.branchtype = tBranchType
and d.branchcode = a.branchcode
and e.branchattr = a.branchattr)
and a.branchtype = tBranchType;
語句沒有較好的過濾條件,優化空間不大,但仍可以稍微改進一點。
以上可改寫爲:
update laprecontinue a
set (managecom,branchcode) =
(select managecom,branchcode
from laagent b
where b.branchtype = tBranchType
and b.agentcode = a.agentcode),
branchattr =
(select branchattr
from laagent b, labranchgroup c
where b.branchcode = c.agentgroup
and c.branchtype = tBranchType
and b.branchtype = tBranchType
and b.agentcode = a.agentcode
)
where not exists (select 1
from laagent d, labranchgroup e
where d.branchtype = tBranchType
and d.agentcode = a.agentcode
and d.branchcode = e.agentgroup
and e.branchtype = tBranchType
and d.branchcode = a.branchcode
and e.branchattr = a.branchattr)
and a.branchtype = tBranchType;
以上只是對update更新寫法進行了簡單修改,但是我不贊成使用上述方式進行關聯更新。關聯更新時,還是推薦使用merge方式。
如果只嵌套一層的話,rownum 限制條件可以謂詞推進到裏層, 這樣就可以保證在裏層就對數據進行很好的過濾。
常見的分頁寫法有如下兩種:
第一種:
select a.* from
(select rownum rn, o.owner, o.object_type, o.object_name, o.object_id from objects
O where owner = 'LIFEBASE') a
where rn <=20 and rn >=1
第二種推薦寫法:
select a.* from
(select rownum rn, o.owner, o.object_type, o.object_name, o.object_id from objects o where owner = 'LIFEBASE') a
where rownum <=20 and rn >=1
或
select a.* from
(select rownum rn, o.owner, o.object_type, o.object_name, o.object_id from objects o where owner = 'LIFEBASE' where rownum <=20) a
where rn >=1
下面通過執行計劃和執行統計信息來說明兩種寫法的區別:
SQL> select a.* from
2 (select rownum rn, o.owner, o.object_type, o.object_name, o.object_id from objects o
3 where owner = 'LIFEBASE') a
4 where rn <=20 and rn >=1;
RN OWNER OBJECT_TYPE OBJECT_NAME OBJECT_ID
----- ------------- ------------------------------------------------------
1 LIFEBASE INDEX IX_BRANCH_INFO_1 352123
2 LIFEBASE INDEX IX_BRANCH_INFO_2 352124
3 LIFEBASE INDEX IX_CHNL_DETAIL_TBL_CODE 352125
4 LIFEBASE INDEX IX_AREA_DEFINE_1 352126
5 LIFEBASE INDEX IX_CITY_1 352127
6 LIFEBASE INDEX IX_DEPARTMENT_INFO_1 352128
7 LIFEBASE INDEX IX_DEPARTMENT_INFO_2 352129
8 LIFEBASE INDEX IX_PROVINCE_1 352130
9 LIFEBASE INDEX IX_COUNTRY_1 352131
10 LIFEBASE INDEX PK_EMP_JOB_TYPE_TBL 346137
11 LIFEBASE INDEX PK_BRANCH_LEVEL_TBL 346139
12 LIFEBASE INDEX PK_BUSINESS_NO 346141
13 LIFEBASE INDEX PK_EMAIL_TYPE 346143
14 LIFEBASE INDEX PK_ID_TYPE 346145
15 LIFEBASE INDEX PK_PREM_PERIOD_TYPE 346147
16 LIFEBASE INDEX PK_DUTY_STATUS 346149
17 LIFEBASE INDEX PK_FIN_ACCOUNT_NO_TYPE 346151
18 LIFEBASE INDEX PK_ADDRESS_TYPE 346153
19 LIFEBASE INDEX PK_INTEREST_UNIT 346155
20 LIFEBASE INDEX PK_DEAL_TYPE 346157
20 rows selected.
Execution Plan
----------------------------------------------------------
Plan hash value: 3882339809
--------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)|
--------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 292 | 35040 | 23 (0)|
|* 1 | VIEW | | 292 | 35040 | 23 (0)|
| 2 | COUNT | | | | |
| 3 | TABLE ACCESS BY INDEX ROWID| OBJECTS | 292 | 10512 | 23 (0)|
|* 4 | INDEX RANGE SCAN | IX_OBJECTS_OWNER_TYPE | 292 | | 2 (0)|
--------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter("RN">=1 AND "RN"<=20)
4 - access("OWNER"='LIFEBASE')
Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
22 consistent gets
0 physical reads
0 redo size
1653 bytes sent via SQL*Net to client
531 bytes received via SQL*Net from client
3 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
20 rows processed
SQL> select a.* from
2 (select rownum rn, o.owner, o.object_type, o.object_name, o.object_id from objects o
3 where owner = 'LIFEBASE') a
4 where rownum <=20 and rn >=1;
RN OWNER OBJECT_TYPE OBJECT_NAME OBJECT_ID
----- ------------- ------------------------------------------------------
1 LIFEBASE INDEX IX_BRANCH_INFO_1 352123
2 LIFEBASE INDEX IX_BRANCH_INFO_2 352124
3 LIFEBASE INDEX IX_CHNL_DETAIL_TBL_CODE 352125
4 LIFEBASE INDEX IX_AREA_DEFINE_1 352126
5 LIFEBASE INDEX IX_CITY_1 352127
6 LIFEBASE INDEX IX_DEPARTMENT_INFO_1 352128
7 LIFEBASE INDEX IX_DEPARTMENT_INFO_2 352129
8 LIFEBASE INDEX IX_PROVINCE_1 352130
9 LIFEBASE INDEX IX_COUNTRY_1 352131
10 LIFEBASE INDEX PK_EMP_JOB_TYPE_TBL 346137
11 LIFEBASE INDEX PK_BRANCH_LEVEL_TBL 346139
12 LIFEBASE INDEX PK_BUSINESS_NO 346141
13 LIFEBASE INDEX PK_EMAIL_TYPE 346143
14 LIFEBASE INDEX PK_ID_TYPE 346145
15 LIFEBASE INDEX PK_PREM_PERIOD_TYPE 346147
16 LIFEBASE INDEX PK_DUTY_STATUS 346149
17 LIFEBASE INDEX PK_FIN_ACCOUNT_NO_TYPE 346151
18 LIFEBASE INDEX PK_ADDRESS_TYPE 346153
19 LIFEBASE INDEX PK_INTEREST_UNIT 346155
20 LIFEBASE INDEX PK_DEAL_TYPE 346157
20 rows selected.
Execution Plan
----------------------------------------------------------
Plan hash value: 781058506
--------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)
--------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 20 | 2400 | 4 (0)
|* 1 | COUNT STOPKEY | | | |
|* 2 | VIEW | | 21 | 2520 | 4 (0)
| 3 | COUNT | | | |
| 4 | TABLE ACCESS BY INDEX ROWID| OBJECTS | 21 | 756 | 4 (0)
|* 5 | INDEX RANGE SCAN | IX_OBJECTS_OWNER_TYPE | 292 | | 2 (0)
--------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter(ROWNUM<=20)
2 - filter("RN">=1)
5 - access("OWNER"='LIFEBASE')
Statistics
----------------------------------------------------------
1 recursive calls
0 db block gets
9 consistent gets
0 physical reads
0 redo size
1653 bytes sent via SQL*Net to client
531 bytes received via SQL*Net from client
3 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
20 rows processed
通過比較可知, 第二種寫法效率比第一種好。
SELECT CUSTOMERNO
FROM LDPERSON
WHERE (NAME = :B5 AND SEX = :B4 AND BIRTHDAY = :B3 AND
(IDTYPE <> :B2 OR
(IDNO <>
CONCAT(SUBSTR(UPPER(:B1), 1, 6), SUBSTR(UPPER(:B1), 9, 9)) AND
CONCAT(SUBSTR(IDNO, 1, 6), SUBSTR(IDNO, 9, 9)) <> UPPER(:B1) AND
IDNO <> UPPER(:B1)) AND
(IDNO <>
CONCAT(SUBSTR(LOWER(:B1), 1, 6), SUBSTR(LOWER(:B1), 9, 9)) AND
CONCAT(SUBSTR(IDNO, 1, 6), SUBSTR(IDNO, 9, 9)) <> LOWER(:B1) AND
IDNO <> LOWER(:B1))))
UNION
SELECT /*+INDEX(LDPERSON LDPERSON_IDX02)*/
CUSTOMERNO
FROM LDPERSON
WHERE (IDTYPE = :B2 AND
(IDNO = UPPER(:B1) OR IDNO = LOWER(:B1) OR
(IDNO = CONCAT(SUBSTR(UPPER(:B1), 1, 6), SUBSTR(UPPER(:B1), 9, 9)) OR
IDNO = CONCAT(SUBSTR(LOWER(:B1), 1, 6), SUBSTR(LOWER(:B1), 9, 9))) OR
(IDNO LIKE
(SUBSTR(LOWER(:B1), 1, 6) || '__' || SUBSTR(LOWER(:B1), 7, 9) || '_') OR
IDNO LIKE
(SUBSTR(UPPER(:B1), 1, 6) || '__' || SUBSTR(UPPER(:B1), 9, 9) || '_'))) AND
(NAME <> :B5 OR SEX <> :B4 OR BIRTHDAY <> :B3))
AND IDTYPE IN ('0', '1')
UNION
SELECT CUSTOMERNO
FROM LDPERSON
WHERE (NAME = :B5 AND SEX = :B4 AND BIRTHDAY = :B3 AND
(IDTYPE <> :B2 OR (IDNO <> UPPER(:B1) AND IDNO <> LOWER(:B1))) AND
IDTYPE NOT IN ('0', '1'))
OR (((IDNO = LOWER(:B1) OR IDNO = UPPER(:B1)) AND IDTYPE = :B2) AND
(NAME <> :B5 OR SEX <> :B4 OR BIRTHDAY <> :B3) AND
IDTYPE IN ('2', '3', '4', 'A'))
分析:
該語句判斷邏輯過於複雜,這將給後期維護帶來較大困難。
還請開發人員檢查邏輯是否可簡化,去掉不必要的邏輯判斷。
當查詢結果集不大時,可以使用。 但是,當結果集很大時,使用標量子查詢的效率就會變得非常糟糕。 當結果集大時,應該用外連接方式對其進行等價改寫!
select t.INT_B as INT58_4280_0_,
t.FLOAT_A as FLOAT59_4280_0_,
t.FLOAT_B as FLOAT60_4280_0_,
t.ST R_AP as STR46_4280_0_,
t.STR_AQ as STR47_4280_0_,
--標量子查詢寫法
(select d.TASK_ID from OBT_TASK_LIST d where d.BUS_ID = t.BUS_ID) as formula164_0_,
(select d.TASK_NAME from OBT_TASK_LIST d where d.BUS_ID = t.BUS_ID) as task_name
from OBT_BUS_LIST t
where 1 = 1
and t.str_j <> '0293661920'
and substr(t.str_b, 1, 4) = '8632'
and substr(t.batch, 1, 8) >=
to_char(add_months(sysdate, -6), 'YYYYMMDD')
and substr(t.batch, 1, 8) <= to_char(sysdate, 'YYYYMMDD')
and (replace(t.str_N, '-', '') = '13770359984' or
replace(t.Str_o, '-', '') = '13770359984' or
replace(t.str_P, '-', '') = '13770359984' or
replace(t.str_An, '-', '') = '13770359984')
分析:
簡單來看,查詢同一個表的兩個字段,導致對 OBT_TASK_LIST 表訪問了兩次,爲減少訪問,可改寫爲關聯查詢。
對於關聯更新,要使用視圖更新或 merge方式,避免使用 update 語句進行關聯更新。
避免使用以下方式,效率很低。
UPDATE POSDATA.POS_VIP_CLIENT_PREPARE PVCP
SET PVCP.STANDARD_PREM =
(SELECT T1.CURRENT_ACCOUNT_VALUE
FROM (SELECT T.*
FROM (SELECT PFC.POLICY_NO,
PFC.PROD_SEQ,
PFC.PRODUCT_CODE,
PAVCH.CURRENT_ACCOUNT_VALUE,
ROW_NUMBER() OVER(PARTITION BY PFC.POLICY_NO ORDER BY PAVCH.TRADING_NO DESC) AS SUBCOUNT
FROM POS_ACCOUNT_VALUE_CHG_HIST PAVCH,
POS_FIN_CONTRACT PFC
WHERE PAVCH.FINANCIAL_CONTRACT_NO =
PFC.FINANCIAL_CONTRACT_NO) T
WHERE T.SUBCOUNT = 1) T1
WHERE T1.POLICY_NO = PVCP.POLICY_NO
AND T1.PROD_SEQ = PVCP.PROD_SEQ
AND T1.PRODUCT_CODE = PVCP.PROD_SEQ)
WHERE PVCP.PRODUCT_CLASS = 'Y'
AND EXISTS
(SELECT 1
FROM POS_FIN_CONTRACT PFC, POS_ACCOUNT_VALUE_CHG_HIST PAVCH
WHERE PVCP.POLICY_NO = PFC.POLICY_NO
AND PVCP.PRODUCT_CODE = PFC.PRODUCT_CODE
AND PVCP.PROD_SEQ = PFC.PROD_SEQ
AND PFC.FINANCIAL_CONTRACT_NO = PAVCH.PRODUCT_TRADING_NO)
應改寫爲:
merge into POSDATA.POS_VIP_CLIENT_PREPARE PVCP
using (SELECT T.*
FROM (SELECT PFC.POLICY_NO,
PFC.PROD_SEQ,
PFC.PRODUCT_CODE,
PAVCH.CURRENT_ACCOUNT_VALUE,
ROW_NUMBER() OVER(PARTITION BY PFC.POLICY_NO ORDER BY PAVCH.TRADING_NO DESC) AS SUBCOUNT
FROM POS_ACCOUNT_VALUE_CHG_HIST PAVCH,
POS_FIN_CONTRACT PFC
WHERE PAVCH.FINANCIAL_CONTRACT_NO =
PFC.FINANCIAL_CONTRACT_NO) T
WHERE T.SUBCOUNT = 1) T1
on ()
when matched then
update ...
另外,對於表中記錄上千萬表的關聯更新, merge into 也會力不從心, 應該使用PL/SQL forall 方式分批對數據進行更新。
該優化的邏輯爲, 使用嵌套視圖優化對一個表的數據進行處理後,再取出部分結果去連接另一張表。
示例如下:
select a.fld1, ...... , b.col1, ......
from tab2 b, tab1 a
where a.key1 = b.key2
and a.fld1 = '10'
order by a.fld2;
以上SQL將如何執行呢?
在連接條件索引沒有異常的情況下, 首先從tab1表中取出滿足a.fld1 = '10' 的記錄,然後根據連接條件進行關聯驗證, 最後對關聯結果集按照a.fld2 進行排序。
如果這個SQL是在分頁場景中,取第一頁數據的話, 這樣的執行順序就是低效的, 因爲雖然只取第一頁數據,但是也必須在兩表關聯之後,對所有數據進行排序。
以上假定在tab1表上沒有創建 fld1,fld2 的複合索引。
如果單獨先處理tab1表,根據 a.fld1 = '10' 條件過濾數據,並按照fld2 進行排序之後,如果在分頁場景中,只需取出少量數據去與tab2 進行連接驗證就可以將結果返回給客戶端了。
這樣就省掉了大量的表關聯操作。
通過嵌套視圖的方式可以達到這種效果:
select a.fld1, ...... , b.col1, ......
from tab2 b,(select a.* from tab1 a where a.fld1 = '10' order by a.fld2) a
where a.key1 = b.key2;
另一種使用嵌套視圖的場景:
select b.部門名, sum(a.銷售額)
from tab1 a, tab2 b
where a.部門編號 = b.部門編號
and a.銷售日期 like '201505%'
group by b.部門名;
以上可以拆解爲以下兩個SQL:
select a.部門編號,sum(a.銷售額) 部門銷售額
from tab1 a
where a.銷售日期 like '201505%'
group by a.部門編號;
select b.部門編號, b.部門名
from tab2 b
where b.部門編號 = :a.部門編號
以上兩個SQL合併的寫法爲:
select b.部門編號, b.部門名, a.部門銷售額
(
select a.部門編號,sum(a.銷售額) 部門銷售額
from tab1 a
where a.銷售日期 like '201505%'
group by a.部門編號
) a, tab2 b
where a.部門編號 = b.部門編號;
這種寫法將先對錶進行聚合操作,再去進行表關聯, 將會大大減少不必要的表連接次數。
這個是藉助使用索引等值查詢效率高的特點。
注意,需要在邏輯等價前提條件下,將範圍查詢條件轉換爲多個等於查詢條件。
select * from tab1 where id between 10 and 13;
可轉換爲:
select * from tab1 where id in (10,11,12,13);
select ......
from tab1 x, tab2 y
where x.key1 = y.key1
and y.key2 between 200501 and 200503; --這裏使用number 類型存儲日期值。
可改寫爲:
select ......
from tab1 x, tab2 y
where x.key1 = y.key1
and y.key2 in (200501,200502,200503);
這種改寫,將會使索引查詢變得高效, 避免對索引引導列進行範圍掃描。
declare
v_cnt pls_integer;
begin
select count(*) into v_cnt from objects;
if v_cnt > 0 then
...
else
...
end;
以上 select count(*) into v_cnt from objects 如何優化呢? 創建索引?
根據上下文可知,v_cnt 就是用來判斷表中是否有數據, 完全沒必要將表中有多少條記錄查詢出來。
所以,可以將查詢語句改寫爲
select count(*) into v_cnt from objects where rownum=1,
這樣改寫之後,就無需彙總查詢了,只要在表中看到記錄,查詢語句就結束了。
有效過濾數據,就是通過查詢條件,從表中只取出很小一部分數據,這樣的字段也稱爲具有很好的可選性,或離散度高。
創建索引時,就是要選擇這樣具有很好可選性的列上來創建索引。
select t.BUS_ID as BUS1_4280_0_,
t.BATCH as BATCH4280_0_,
t.BUS_TYPE as BUS3_4280_0_,
t.POLICYNUM as POLICYNUM4280_0_,
t.STR_A as STR5_4280_0_,
t.STR_B as STR6_4280_0_,
t.STR_C as STR7_4280_0_,
t.STR_D as STR8_4280_0_,
t.STR_E as STR9_4280_0_,
t.STR_F as STR10_4280_0_,
t.STR_G as STR11_4280_0_,
t.STR_H as STR12_4280_0_,
t.STR_I as STR13_4280_0_,
t.STR_J as STR14_4280_0_,
t.STR_K as STR15_4280_0_,
t.STR_L as STR16_4280_0_,
t.STR_M as STR17_4280_0_,
t.STR_N as STR18_4280_0_,
t.STR_O as STR19_4280_0_,
t.STR_P as STR20_4280_0_,
t.STR_Q as STR21_4280_0_,
t.STR_R as STR22_4280_0_,
t.STR_S as STR23_4280_0_,
t.STR_T as STR24_4280_0_,
t.STR_U as STR25_4280_0_,
t.STR_V as STR26_4280_0_,
t.STR_W as STR27_4280_0_,
t.STR_X as STR28_4280_0_,
t.STR_Y as STR29_4280_0_,
t.STR_Z as STR30_4280_0_,
t.STR_AA as STR31_4280_0_,
t.STR_AB as STR32_4280_0_,
t.STR_AC as STR33_4280_0_,
t.STR_AD as STR34_4280_0_,
t.STR_AE as STR35_4280_0_,
t.STR_AF as STR36_4280_0_,
t.STR_AG as STR37_4280_0_,
t.STR_AH as STR38_4280_0_,
t.STR_AI as STR39_4280_0_,
t.STR_AJ as STR40_4280_0_,
t.STR_AK as STR41_4280_0_,
t.STR_AL as STR42_4280_0_,
t.STR_AM as STR43_4280_0_,
t.STR_AN as STR44_4280_0_,
t.STR_AO as STR45_4280_0_,
t.STR_AV as STR52_4280_0_,
t.STR_AW as STR53_4280_0_,
t.STR_AX as STR54_4280_0_,
t.STR_AY as STR55_4280_0_,
t.STR_AZ as STR56_4280_0_,
t.INT_A as INT57_4280_0_,
t.INT_B as INT58_4280_0_,
t.FLOAT_A as FLOAT59_4280_0_,
t.FLOAT_B as FLOAT60_4280_0_,
t.ST R_AP as STR46_4280_0_,
t.STR_AQ as STR47_4280_0_,
(select d.TASK_ID from OBT_TASK_LIST d where d.BUS_ID = t.BUS_ID) as formula164_0_
from OBT_BUS_LIST t
where 1 = 1
and t.str_j <> '0293661920'
and substr(t.str_b, 1, 4) = '8632'
and substr(t.batch, 1, 8) >=
to_char(add_months(sysdate, -6), 'YYYYMMDD')
and substr(t.batch, 1, 8) <= to_char(sysdate, 'YYYYMMDD')
and (replace(t.str_N, '-', '') = '13770359984' or
replace(t.Str_o, '-', '') = '13770359984' or
replace(t.str_P, '-', '') = '13770359984' or
replace(t.str_An, '-', '') = '13770359984')
創建索引:
create index focus.ix_obt_bus_list_str_n_f on
focus.OBT_BUS_LIST(replace(t.str_n, '-', '')) ;
create index focus.ix_obt_bus_list_str_o_f on
focus.OBT_BUS_LIST(replace(t.str_o, '-', '')) ;
create index focus.ix_obt_bus_list_str_p_f on
focus.OBT_BUS_LIST(replace(t.str_p, '-', '')) ;
create index focus.ix_obt_bus_list_str_an_f on
focus.OBT_BUS_LIST(replace(t.str_an, '-', ''));
創建索引前,單次執行6秒, 創建後,單次執行 0.3 秒。
select *
from (select a.summary_id as SUMMARY1_4147_0_,
a.summary_time as SUMMARY3_4147_0_,
a.agent_id as AGENT4_4147_0_,
a.ani as ANI4147_0_,
a.cust_name as CUST5_4147_0_,
c.name as CUST8_4147_0_,
a.channal_type as CHANNAL9_4147_0_,
a.call_type as CALL6_4147_0_,
'0' as HIS7_4147_0_
from focus.SUMMARY_DAY a, focus.BAS_DIC c
where 1 = 1
and c.no = a.cust_type
and c.dic_id = 'CUSTOM_FL'
and a.summary_time >= '20150205150027'
and a.summary_time <= '20150205153419'
and a.agent_id = '黨靜'
order by a.summary_time desc)
where rownum <= 10
創建索引:
create index focus.ix_summary_day_agid_subtime on
focus.summary_day(agent_id,summary_time desc)
nologging tablespace datafocus;
因爲索引相對錶來說體積較小,所以,創建索引,可以減少數據讀取,提高聚合操作的執行效率。
SELECT COUNT(*)
FROM RN_PAID_INFO S
WHERE S.PREM_ACTURAL_DATE >= TO_DATE('2014-01-01', 'yyyy-mm-dd')
AND S.PREM_ACTURAL_DATE <= TO_DATE('2014-04-01', 'yyyy-mm-dd')
AND S.AMT_TYPE IN ('005', '901', '801', '02F', '03F', '222', '22A')
索引測試情況:
PREM_ACTURAL_DATE有索引,不添加amt_type 查詢時,0.1s 就能查出結果。添加 amt_type 條件後,10秒以上。
爲什麼多條件一個條件,反而效率更差了呢? 因爲要進行額外的校驗操作。
在amt_type 上添加索引後,4秒查出結果。
改用amt_type、PREM_ACTURAL_DATE 複合索引後,0.04秒查詢出結果。
SQL> set timing on;
SQL> SELECT COUNT(*)
2 FROM RN_PAID_INFO S
3 WHERE S.PREM_ACTURAL_DATE >= TO_DATE('2014-01-01', 'yyyy-mm-dd')
4 AND S.PREM_ACTURAL_DATE <= TO_DATE('2014-04-01', 'yyyy-mm-dd');
COUNT(*)
-------------
1073075
Elapsed: 00:00:00.06
Execution Plan
----------------------------------------------------------
Plan hash value: 2105428200
---------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 8 | 2528 (1)| 00:00:31 |
| 1 | SORT AGGREGATE | | 1 | 8 | | |
|* 2 | INDEX RANGE SCAN| IX_RN_PAID_INFO_3 | 1058K| 8269K| 2528 (1)| 00:00:31 |
---------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access("S"."PREM_ACTURAL_DATE">=TO_DATE(' 2014-01-01 00:00:00',
'syyyy-mm-dd hh24:mi:ss') AND "S"."PREM_ACTURAL_DATE"<=TO_DATE(' 2014-04-01
00:00:00', 'syyyy-mm-dd hh24:mi:ss'))
Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
2558 consistent gets
0 physical reads
0 redo size
529 bytes sent via SQL*Net to client
519 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1 rows processed
SQL> SELECT /*+ no_index(s ix_rn_paid_info_amt_pradate) */ COUNT(*)
2 FROM RN_PAID_INFO S
3 WHERE S.PREM_ACTURAL_DATE >= TO_DATE('2014-01-01', 'yyyy-mm-dd')
4 AND S.PREM_ACTURAL_DATE <= TO_DATE('2014-04-01', 'yyyy-mm-dd')
5 AND S.AMT_TYPE IN ('005', '901', '801', '02F', '03F', '222', '22A');
COUNT(*)
-------------
329066
Elapsed: 00:00:02.86
Execution Plan
----------------------------------------------------------
Plan hash value: 2779815793
-----------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 12 | 146K (1)| 00:29:23 |
| 1 | SORT AGGREGATE | | 1 | 12 | | |
|* 2 | TABLE ACCESS FULL| RN_PAID_INFO | 353K| 4137K| 146K (1)| 00:29:23 |
-----------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - filter("S"."PREM_ACTURAL_DATE"<=TO_DATE(' 2014-04-01 00:00:00',
'syyyy-mm-dd hh24:mi:ss') AND "S"."PREM_ACTURAL_DATE">=TO_DATE('
2014-01-01 00:00:00', 'syyyy-mm-dd hh24:mi:ss') AND ("S"."AMT_TYPE"='005'
OR "S"."AMT_TYPE"='02F' OR "S"."AMT_TYPE"='03F' OR "S"."AMT_TYPE"='222' OR
"S"."AMT_TYPE"='22A' OR "S"."AMT_TYPE"='801' OR "S"."AMT_TYPE"='901'))
Statistics
----------------------------------------------------------
1 recursive calls
0 db block gets
538344 consistent gets
538249 physical reads
0 redo size
528 bytes sent via SQL*Net to client
519 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1 rows processed
SQL> SELECT COUNT(*)
2 FROM RN_PAID_INFO S
3 WHERE S.PREM_ACTURAL_DATE >= TO_DATE('2014-01-01', 'yyyy-mm-dd')
4 AND S.PREM_ACTURAL_DATE <= TO_DATE('2014-04-01', 'yyyy-mm-dd')
5 AND S.AMT_TYPE IN ('005', '901', '801', '02F', '03F', '222', '22A');
COUNT(*)
----------
328516
Elapsed: 00:00:00.03
Execution Plan
----------------------------------------------------------
Plan hash value: 3408632279
---------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)|
---------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 12 | 1237 (1)|
| 1 | SORT AGGREGATE | | 1 | 12 | |
| 2 | INLIST ITERATOR | | | | |
|* 3 | INDEX RANGE SCAN| IX_RN_PAID_INFO_AMT_PRADATE | 386K| 4528K| 1237 (1)|
---------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
3 - access(("S"."AMT_TYPE"='005' OR "S"."AMT_TYPE"='02F' OR "S"."AMT_TYPE"='03F' OR
"S"."AMT_TYPE"='222' OR "S"."AMT_TYPE"='22A' OR "S"."AMT_TYPE"='801' OR
"S"."AMT_TYPE"='901') AND "S"."PREM_ACTURAL_DATE">=TO_DATE(' 2014-01-01 00:00:00',
'syyyy-mm-dd hh24:mi:ss') AND "S"."PREM_ACTURAL_DATE"<=TO_DATE(' 2014-04-01 00:00:00',
'syyyy-mm-dd hh24:mi:ss'))
Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
1069 consistent gets
0 physical reads
0 redo size
528 bytes sent via SQL*Net to client
520 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1 rows processed
SQL> SELECT COUNT(*) FROM RN_PAID_INFO S;
COUNT(*)
-------------
15460103
Elapsed: 00:00:00.39 --爲什麼這個查詢比上面的還慢呢?
Execution Plan
----------------------------------------------------------
Plan hash value: 1747368817
-----------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Cost (%CPU)|
-----------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 7314 (2)|
| 1 | SORT AGGREGATE | | 1 | |
| 2 | INDEX FAST FULL SCAN| IX_RN_PAID_INFO_CHANNEL_TYPE | 15M| 7314 (2)|
-----------------------------------------------------------------------------------
Statistics
----------------------------------------------------------
1 recursive calls
0 db block gets
27234 consistent gets
0 physical reads
0 redo size
529 bytes sent via SQL*Net to client
519 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1 rows processed
爲什麼帶有查詢條件的count(*) 比不帶查詢條件的count(*) 執行還快呢?
因爲要查詢的數據少啊。
select rsk.riskname RiskName,
case
when (select SubRiskFlag
from LMRiskApp b
where b.riskcode = rsk.riskcode) = 'M' then
1
else
0
end IsMainProd,
rsk.risktype RiskType,
rsk.risktype1 RiskType1,
rsk.risktype3 RiskType3,
rsk.risktype4 RiskType4,
rsk.risktype5 RiskType5,
rsk.riskperiod RiskPeriod,
rsk.risktypedetail RiskTypeDetail,
case
when exists
(select 1
from lcpol l
where riskcode = rsk.riskcode
and riskcode in
(select code from ldcode1 where codetype = 'HMCP')) then
1
else
0
end IsWaivedProd,
'411403' MainRiskCode,
(select *
from (select salestartdate
from ldrisksalestate
where riskcode = '411403'
and managecom in ('86',
'8611',
'861101')
order by managecom desc)
where rownum = 1) salestartdate,
(select *
from (select saleenddate
from ldrisksalestate
where riskcode = '411403'
and managecom in ('86', '8611', '861101')
order by managecom desc)
where rownum = 1) saleenddate,
(select *
from (select SaleState from ldrisksalestate where
riskcode = '411403' and
managecom in ('86', '8611', '861101') order by managecom desc)
where rownum = 1) SaleStatus
from LMRiskApp rsk
where rsk.riskcode = '411403'
Elapsed: 00:00:34.62
Execution Plan
----------------------------------------------------------
Plan hash value: 1485715133
---------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)|
---------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 48 | 1 (0)|
| 1 | TABLE ACCESS BY INDEX ROWID | LMRISKAPP | 1 | 9 | 1 (0)|
|* 2 | INDEX UNIQUE SCAN | PK_LMRISKAPP | 1 | | 0 (0)|
| 3 | NESTED LOOPS | | 1 | 26 | 5464 (1)|
|* 4 | INDEX RANGE SCAN | PK_LDCODE1 | 1 | 19 | 3 (0)|
|* 5 | INDEX FAST FULL SCAN | PK_LCPOL_2 | 2 | 14 | 5461 (1)|
|* 6 | COUNT STOPKEY | | | | |
| 7 | VIEW | | 1 | 9 | 2 (0)|
| 8 | TABLE ACCESS BY INDEX ROWID | LDRISKSALESTATE | 1 | 14 | 2 (0)|
|* 9 | INDEX RANGE SCAN DESCENDING| SYS_C0045869 | 1 | | 1 (0)|
|* 10 | COUNT STOPKEY | | | | |
| 11 | VIEW | | 1 | 9 | 2 (0)|
| 12 | TABLE ACCESS BY INDEX ROWID | LDRISKSALESTATE | 1 | 17 | 2 (0)|
|* 13 | INDEX RANGE SCAN DESCENDING| SYS_C0045869 | 1 | | 1 (0)|
|* 14 | COUNT STOPKEY | | | | |
| 15 | VIEW | | 1 | 3 | 2 (0)|
| 16 | TABLE ACCESS BY INDEX ROWID | LDRISKSALESTATE | 1 | 13 | 2 (0)|
|* 17 | INDEX RANGE SCAN DESCENDING| SYS_C0045869 | 1 | | 1 (0)|
| 18 | TABLE ACCESS BY INDEX ROWID | LMRISKAPP | 1 | 48 | 1 (0)|
|* 19 | INDEX UNIQUE SCAN | PK_LMRISKAPP | 1 | | 0 (0)|
---------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access("B"."RISKCODE"=:B1)
4 - access("CODETYPE"='HMCP' AND "CODE"=:B1)
5 - filter("RISKCODE"=:B1 AND "RISKCODE"="CODE")
6 - filter(ROWNUM=1)
9 - access("RISKCODE"='411403')
filter("MANAGECOM"='86' OR "MANAGECOM"='8611' OR "MANAGECOM"='861101')
10 - filter(ROWNUM=1)
13 - access("RISKCODE"='411403')
filter("MANAGECOM"='86' OR "MANAGECOM"='8611' OR "MANAGECOM"='861101')
14 - filter(ROWNUM=1)
17 - access("RISKCODE"='411403')
filter("MANAGECOM"='86' OR "MANAGECOM"='8611' OR "MANAGECOM"='861101')
19 - access("RSK"."RISKCODE"='411403')
Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
1020699 consistent gets
128050 physical reads
2004 redo size
1450 bytes sent via SQL*Net to client
505 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1 rows processed
創建索引:
create index lisprod.ix_lcpol_riskcd on lisprod.lcpol(riskcode)
nologging tablespace lisindex;
創建索引後執行時間:
Elapsed: 00:00:00.04
Execution Plan
----------------------------------------------------------
Plan hash value: 1228450184
---------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)|
---------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 48 | 1 (0)|
| 1 | TABLE ACCESS BY INDEX ROWID | LMRISKAPP | 1 | 9 | 1 (0)|
|* 2 | INDEX UNIQUE SCAN | PK_LMRISKAPP | 1 | | 0 (0)|
| 3 | NESTED LOOPS | | 1 | 26 | 35 (3)|
|* 4 | INDEX RANGE SCAN | PK_LDCODE1 | 1 | 19 | 3 (0)|
|* 5 | INDEX RANGE SCAN | IX_LCPOL_RISKCD | 2 | 14 | 32 (4)|
|* 6 | COUNT STOPKEY | | | | |
| 7 | VIEW | | 1 | 9 | 2 (0)|
| 8 | TABLE ACCESS BY INDEX ROWID | LDRISKSALESTATE | 1 | 14 | 2 (0)|
|* 9 | INDEX RANGE SCAN DESCENDING| SYS_C0045869 | 1 | | 1 (0)|
|* 10 | COUNT STOPKEY | | | | |
| 11 | VIEW | | 1 | 9 | 2 (0)|
| 12 | TABLE ACCESS BY INDEX ROWID | LDRISKSALESTATE | 1 | 17 | 2 (0)|
|* 13 | INDEX RANGE SCAN DESCENDING| SYS_C0045869 | 1 | | 1 (0)|
|* 14 | COUNT STOPKEY | | | | |
| 15 | VIEW | | 1 | 3 | 2 (0)|
| 16 | TABLE ACCESS BY INDEX ROWID | LDRISKSALESTATE | 1 | 13 | 2 (0)|
|* 17 | INDEX RANGE SCAN DESCENDING| SYS_C0045869 | 1 | | 1 (0)|
| 18 | TABLE ACCESS BY INDEX ROWID | LMRISKAPP | 1 | 48 | 1 (0)|
|* 19 | INDEX UNIQUE SCAN | PK_LMRISKAPP | 1 | | 0 (0)|
---------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access("B"."RISKCODE"=:B1)
4 - access("CODETYPE"='HMCP' AND "CODE"=:B1)
5 - access("RISKCODE"="CODE")
filter("RISKCODE"=:B1)
6 - filter(ROWNUM=1)
9 - access("RISKCODE"='411403')
filter("MANAGECOM"='86' OR "MANAGECOM"='8611' OR "MANAGECOM"='861101')
10 - filter(ROWNUM=1)
13 - access("RISKCODE"='411403')
filter("MANAGECOM"='86' OR "MANAGECOM"='8611' OR "MANAGECOM"='861101')
14 - filter(ROWNUM=1)
17 - access("RISKCODE"='411403')
filter("MANAGECOM"='86' OR "MANAGECOM"='8611' OR "MANAGECOM"='861101')
19 - access("RSK"."RISKCODE"='411403')
Statistics
----------------------------------------------------------
322 recursive calls
0 db block gets
110 consistent gets
0 physical reads
0 redo size
1450 bytes sent via SQL*Net to client
492 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
5 sorts (memory)
0 sorts (disk)
1 rows processed
開發人員在SQL書寫過程中規避掉一些低級錯誤,就基本上算是寫好了SQL。接下來,數據庫優化器負責給SQL提供一個高效率的執行計劃。
但是,假如開發人員沒有創建合適的索引,數據庫就沒有辦法提供最高效率的執行計劃,因爲高效執行計劃是建立在合適訪問路徑存在的基礎之上的。
所以,開發工程師除了寫好SQL,還應該負責在適當的字段上創建索引。因爲開發最瞭解應用程序對錶中數據的使用方式和表中數據的分佈情況。
作爲一個合格的開發人員,不僅應該關注完美功能的實現,還應該關注應用程序的執行效率、用戶體驗。如果應用響應緩慢,那麼,用戶一定會失去耐心的。
其實,性能優化的過程,就是一個學會判斷哪些是最消耗資源的操作,並想辦法繞過它的過程。 只要這樣,我們就實現了優化。