平時工作的時候,經常會遇到這種事情,從一個大表A中,抽取字段a在一個相對較小B的表的數據,比如,從一個詳單表中,抽取幾萬個用戶號碼的話單出來。這種時候,一般來說,
做關聯查詢:
create table A1 as select A.* from 詳單表 A,號碼表 B wherea.號碼 = b.號碼
當然這個語句根據情況不一樣有很多中寫法,但是,不管是哪種寫法,這個語句最大的問題是,如果數據量大,你非常糾結他到底能不能跑完,有時候一個事情,要求2個小時干完,現在跑了1個小時,你說到底要不要結束它另外想辦法呢?
所以,這種時候,還是讓事情可控一點比較好,首先,還是要考慮a字段的特征,如果a字段並集很大,A有上億,a字段有幾百萬,這種時候,我們會給a字段建上索引,比如我要抽取1萬個號碼的詳單,這樣寫其實就很好:
for x in (select 號碼 from Bwhere B.狀態="未處理") loop
insert into A1 select * from A where a.號碼=x.號碼 ;
update B set B.狀態=已處理 where B.號碼=x.號碼 ;
commit ;
end loop;
這樣的好處,第一你可以看到執行了多少,進度可控,第二,你隨時可以停了他繼續。下次啟動時,前面做完的他就不會繼續做了。
但是,當a字段的並集,不是那麼大,而是比較小的時候,比如只有十萬的時候, 就不怎麼舒服了,因為這種時候索引就沒那麼管用了。當然你還是可以按照上面的方式那樣去做,只是會很慢很慢。所以,這個時候考慮完全做一次全表掃描,實際比索引字段查詢多次還要快(為了貼近實際,我把這個字段叫做文件名跟上面的號碼區分):
for x in (select rowid,文件名 from A)
select count(1) into cc from B where B.文件名=x.文件名 ;
if cc >= 1 then
insert into A1 select * from A where A.rowid=x.rowid;
commit ;
end if ;
end loop;
上面語句中,其實這麼寫是偷懶的做法,因為要插一條記錄去新表A1中,理想的辦法當然是在游標中讀取所有字段,然後直接insert 到A1中,用insert into A1 values這種,如果寫成insert into A1 select * from A where A.rowid=x. rowid會增加IO,實際上,rowid定位一行數據是極快的方法(不管你表有多大,都是一樣的效率),這種寫法根本不會比insert into A1 values 慢多少。
再次但是,這種場景下,其實是容易出問題,問題出在這個數據量很大,且記錄不怎麼連續的時候,當記錄不怎麼連續時,游標fentch到越後面,就越慢。一個億的記錄,沒准在幾百萬的時候就卡住了。出現問題的原因,還是因為記錄不夠連續,導致尋址變慢,解決的辦法,就是重建這個表(或者對這個表做表分析)。
再次再次但是,上億的表,做表分析也好,還是重建,都不容易,可能重建個幾個小時還是建不出來,而且浪費空間。所以,上面那個是想偷懶少些字段的邏輯,就變成了必然,我們考慮重建表的時候,只需要兩個字段:
create table A1的映射 nolloggingas select rowid as rrowid , 文件名 from A ;
for x in (select rrowid,文件名 from A1的映射)
select count(1) into cc from B where B.文件名=x.文件名 ;
if cc >= 1 then
insert into A1 select * from A where A.rowid=x.rrowid;
commit ;
end if ;
end loop;
重建表的時候,只用兩個字段,可以大大減少重建的時間(create table 其實是獲取的數據量越大越慢,還不是線性的,是幾何增長的)。然後游標使用新的重建表,讀取rowid字段(已經改為了rrowid),在根據獲取到的rowid去實際表中獲取數據。
要不怎麼說,這個世界是懶人推動的呢?