1. 程式人生 > >DB2中exists與in的效率對比 — 5秒與21分鐘的差距

DB2中exists與in的效率對比 — 5秒與21分鐘的差距

在SQL查詢優化中,無論是ORACLE,SQL Server,還是DB2,或者其他資料庫產品,都有這樣一條優化規則:儘量使用exists去替代查詢中的in。

但是關於他們之間的查詢效率,並沒有給出太多的實際查詢對比,都只是看書上寫,或者聽“牛人”說,就信以為真了;

那麼他們之間的效率差距到底有多大呢?我們來看下吧!

前幾天一同事,需要找些資料,然後自己嘗試寫了這個查詢語句,寫了一會,寫不下去了,然後向我“求救”。

她的需求是這樣的:

1. 查詢這樣的卡號,卡號對應的賬戶,賬戶狀態正常(為'0'),賬戶擴充套件標誌前5為也正常('00000');

2. 卡號為已經領取的卡(為'1');

3. 開卡機構所屬的城市程式碼不為'790'。

這個需求中,涉及到4張表,遮蔽寫資訊:

卡管理物理檔案(表):(Key—卡號,儲存卡號,以及卡的各種狀態)(as400上的物理檔案,相當於DB2中的表)

卡號賬號對照表:(Key—賬號,通過卡號,取到對應的賬號,同時儲存卡號和賬號)

活期存款帳戶動態表: (Key—客戶號,儲存賬戶資訊,包括賬戶狀態,賬號;賬號與卡號賬號對照表關聯)

網點(營業機構)資訊 表:(Key—網點,儲存網點資訊,開卡機構與卡管理表關聯)

我們先將上面的需求分解,不管多麼複雜的需求,分解之後,都變得簡單,下面的查詢中遮蔽了一些敏感資訊;

開卡機構所屬的城市程式碼不為'790':

select e.網點                       
  from 網點資訊表 e                  
 where e.城市程式碼 <> '790'

賬戶狀態正常,賬戶擴充套件標誌前5位為'00000':
select a.卡號 crdno            
  from 卡號賬號對照表 a,活期存款賬戶動態表 b        
 where a.賬號 = b.賬號         
   and b.賬戶狀態 = '0'              
   and b.賬戶擴充套件標誌 like '000000%'

然後將這些分支SQL組合起來,就實現了需求。

select c.*                         
  from 卡管理表 c,(                  
select a.卡號 crdno            
  from 卡號賬號對照表 a,活期存款賬戶動態表 b        
 where a.賬號 = b.賬號         
   and b.賬戶狀態 = '0'              
   and b.賬戶擴充套件標誌 like '000000%') d 
 where c.卡號 = d.crdno        
   and c.卡流轉狀態='1'             
   and c.開卡網點 in (            
select e.網點                       
  from 網點資訊表 e                  
 where e.城市程式碼 <> '790')

然後執行,真希望馬上就能出資料,可是:

時間一分一秒的過去,為了看下這條語句能執行多長時間,我還特意複製了資料處理情況;

結果大約2012-11-21 19:27的時候,這條語句才執行結束,資料出來了,開始執行的大概時間為:2012-11-21 19:05~19:10;

也就是說,這條sql查詢運行了大概20分鐘!哇!

這是21號執行的時間結果,今天我又重新運行了一下這個語句,執行情況如下:

開始:2012-11-24 15:35:00
處理狀態:
F3=Exit   F4=Prompt   F6=Insert line   F9=Retrieve   F10=Copy line 
F12=Cancel            F13=Services     F24=More keys               
Query running. 28148734 records selected, 20370384 processed. 
Query running. 31133092 records selected, 22545088 processed.
Query running. 34895061 records selected, 25299477 processed.
Query running. 40745687 records selected, 29560372 processed.
Query running. 50737215 records selected, 36759514 processed. 
15minutes
Query running. 53872254 records selected, 39018439 processed. 
16minutes
Query running. 59911556 records selected, 43478566 processed.  
17minutes
Query running. 64619276 records selected, 46935096 processed. 
18minutes
Query running. 70084019 records selected, 50945419 processed.
19minutes
Query running. 74880947 records selected, 54461392 processed. 
20minutes
Query running. 79317109 records selected, 57668874 processed.
21minutes
Query running. 86032895 records selected, 63412911 processed.
結束:2012-11-24 15:56:05

對於這樣的sql語句,真讓人頭疼,費那麼大的勁才把資料讀出來,真有點黃花菜都涼了的感覺;

下面我們來看下,高效率的sql吧!同一個查詢語句,只需稍微改動,將in替換為exists,效率可是高得驚人哦!

  select c.*
  from 卡管理檔案 c,(
select a.卡號 crdno
  from 卡號賬號對照表 a,活期存款賬戶動態表 b
 where a.賬號 = b.賬號
   and b.賬戶狀態 = '0'
   and b.賬戶擴充套件標誌 like '000000%' ) d
 where c.卡號 = d.crdno
   and c.卡流轉狀態='1'
   and not exists (
select 1
  from 網點資訊表 e
 where c.開卡網點 = e.網點
   and e.城市程式碼 = '790'
)

每次執行這個查詢,不到5秒鐘,資料就出來了,真的不敢相信!讀取到的是同樣的資料,效率差距怎麼就這麼大呢!

5秒 vs 21分鐘

下面是這些表中的資料統計:

select count(*) from 卡管理檔案
....+....1....
  COUNT ( * ) 
    4,837,343 
---------------------------
SELECT count(*) FROM 卡管理檔案 WHERE 卡流轉狀態 ='1'  
....+....1.... 
  COUNT ( * )  
    4,340,909  
---------------------------
select count(*) from 卡號賬號對找表
....+....1...
  COUNT ( * )
    4,786,131
---------------------------
select count(*) from 活期存款賬戶動態表
....+....1....
  COUNT ( * ) 
   24,325,998 
---------------------------
select count(*) from 網點資訊表
....+....1....
  COUNT ( * ) 
        3,018 
----------------------------
select count(*)       
  from 網點資訊       
 where 城市程式碼 <> '790' 
 ....+....1....   
   COUNT ( * )    
         2,624    
---------------------------
select count(*)
  from (
select a.k22crdno crdno
  from 卡號賬號對照表 a,活期存款賬戶動態表 b
 where a.賬號 = b.賬號
   and b.賬戶狀態 = '0'
   and b.賬戶擴充套件標誌 like '000000%') a
 ....+....1....
   COUNT ( * ) 
     3,773,673 
最終這個查詢,讀取到的資料:
select count(*) from (...) a
 ....+....1....    
   COUNT ( * )     
     3,369,145  
--這裡要注意下,統計時,要給子查詢取個別名

--the end--