有時候開發人員寫sql語句的時候,接觸的性能問題越多,可能對sql語句的結構,性能考慮會多一些,這也是一件好事,不過如果考慮不當,本來原本想做的的一些優化卻使得問題變得更加嚴重。
在生産環境中我們對指定的用戶端都有一定的監控,在下午的時候發現一個sql語句執行的時間太長了,抓到語句,一看倒不複雜,是一個開發人員運作的。
從他的sql語句可以看出他在嘗試自連接配接account表(account表示百萬級别的),l9_id不是account的主鍵,ban這個字段是主鍵字段。
select *
from account a
where l9_id = 'XX'
and l9_id is not null
and exists (select 1
from account
where l9_id = a.l9_id
and ban a.ban
and rownum = 1)
order by a.l9_id
他要做的查詢從結構上來看類似下面的形式,比如表account裡的資料如下
ban l9_id
1 1001
2 1001
3 1002
4 1001
5 1002
每個account對應一個l9_id字段,是不唯一的,比如根據account ban=1得到l9_id=1001,現在要查的是除了ban=1之外的l9_id為1001的ban清單。
從sql結構中可以看到,開發人員還專門使用了exists rownum,看起來好像還不錯。
來看看oracle生成的執行計劃。
你會看到cost已經到1886M,時間是999:59:59,從計劃來看,oracle都不知道什麼時候能執行完成。
的确從生成環境來看,這條語句執行很慢,用了2個小時。
SQL> @plan
Plan hash value: 2418382151
--------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes |TempSpc| Cost (%CPU)| Time |
| 0 | SELECT STATEMENT | | 1 | 204 | | 1886M (1)|999:59:59 |
| 1 | SORT ORDER BY | | 1 | 204 | 537M| 1886M (1)|999:59:59 |
|* 2 | FILTER | | | | | | |
|* 3 | TABLE ACCESS FULL | ACCOUNT | 1651K| 321M| | 33945 (1)| 00:06:48 |
|* 4 | COUNT STOPKEY | | | | | | |
|* 5 | TABLE ACCESS FULL| ACCOUNT | 2 | 26 | | 1146 (1)| 00:00:14 |
Predicate Information (identified by operation id):
---------------------------------------------------
2 - filter( EXISTS (SELECT 0 FROM "ACCOUNT" "ACCOUNT" WHERE
ROWNUM=1 AND "L9_ID"=:B1 AND "BAN":B2))
3 - filter("L9_COMPANY_CODE"='XX' AND "L9_ID" IS NOT NULL)
4 - filter(ROWNUM=1)
5 - filter("L9_ID"=:B1 AND "BAN":B2)
可以舉個笑話來說明一下,這個邏輯的問題,
有一天,小白兔到面包房買面包,問老闆,“老闆啊,你有100個面包嗎?",老闆說沒有啊,小白兔第二天又去買面包,又問老闆有沒有100個面包啊。老闆還是說沒有,第三天的時候老闆忙了很久終于做好了100個面包,小白兔又來買面包了,問老闆你又100個面包嗎,老闆高興的說有啊,小白兔也高興的說,給我買一個!
如果仔細分析邏輯,就會發現那個rownum是畫蛇添足,本來可以順利得到ban的清單,但是反複循環,循環幾百萬次,每次都是一個全表掃描,還不一定能夠查到對應的ban值。
對于這個語句,其實不用那麼複雜。寫成下面的形式就好。基本沒有什麼特别的地方。
select t1.ban from account t1,account t2
where t1.id = 'TD'
and t1.id is not null and t2.l9_id is not null
and t1.l9_id=t2.l9_lid
and t1.ban!=t2.ban
執行計劃來看确實是一個可以達到目标的計劃。
Plan hash value: 1286362100
------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes |TempSpc| Cost (%CPU)| Time |
| 0 | SELECT STATEMENT | | 48M| 1353M| | 78038 (2)| 00:15:37 |
|* 1 | HASH JOIN | | 48M| 1353M| 44M| 78038 (2)| 00:15:37 |
|* 2 | TABLE ACCESS FULL| ACCOUNT | 1651K| 25M| | 33924 (1)| 00:06:48 |
|* 3 | TABLE ACCESS FULL| ACCOUNT | 6605K| 81M| | 33915 (1)| 00:06:47 |
是以,有些東西還是大道至簡的道理,其實oracle内部也做了很多的優化,對于exists和in已經沒有那麼明顯的差别了。而且它會從資料的分布角度進行計劃的解析。從第一個執行計劃來看,資料庫分析的還是很合理的。