您好,登錄后才能下訂單哦!
日常生活中,我們會遇到各種各樣的數據,小到公司通訊錄,大到互聯網用戶行為分析。在進行數據分析處理的過程中,查詢是必不可少的環節,如何更加高效地進行數據查詢。
SPL為用戶提供了強大的索引機制以及針對不同場景中各對象的查詢函數,善加運用,可以顯著提高查詢性能。
我們先建立一個份“通話記錄”的模擬數據,通過這份數據,來比較一下不同查詢函數對序表查詢性能的影響。建立模擬數據的代碼如下:
A | |
1 | =5000000.new(13800000000+~:Subscriber,datetime(1547123785410+rand(864000000)):Time,rand(3600)+1:Length,rands("TF",1):isLocal) |
2 | =file("btx/voiceBill.btx").export@b(A1) |
代碼1.1.1
其中部分數據如下:
圖1.1.1
對序表進行查詢,通常我們會想到使用A.select()函數。我們來看一下使用該函數的效果:
A | B | |
1 | =file("btx/voiceBill.btx").import@b() | /讀取文件中的數據,作為序表 |
2 | =now() | /當前時間 |
3 | =A1.select(Subscriber==13800263524) | /使用A.select()函數進行查詢 |
4 | =interval@ms(A2,now()) | /查詢耗時 |
代碼1.1.2
查詢耗時為80毫秒。
對序表的鍵值進行查詢時,可以利用A.find()函數進行查詢。示例代碼如下:
A | B | |
1 | =file("btx/voiceBill.btx").import@b() | /讀取文件中的數據,作為序表 |
2 | >A1.keys(Subscriber) | /設置Subscriber為主鍵 |
3 | >A1.index() | /建立索引 |
4 | =now() | /當前時間 |
5 | =A1.find(13800263524) | /使用A.find()函數查找 |
6 | =interval@ms(A4,now()) | /查詢花費的時間 |
代碼1.1.3
查詢耗時為1毫秒。
這是因為在集算器的序表中,可以指定某個或某些字段作為主鍵,基于主鍵的查找可以使用專門的函數。比如代碼1.1.3中A5的find函數,不僅能簡化書寫,更能有效地提高計算性能。
當鍵值較多時,我們使用函數A.find@k ()進行批量鍵值查找。示例代碼如下:
A | B | |
1 | =file("btx/voiceBill.btx").import@b() | /讀取文件中的數據,作為序表 |
2 | >A1.keys(Subscriber) | /設置Subscriber為主鍵 |
3 | >A1.index() | /建立索引 |
4 | =A1(50.(rand(A1.len())+1)).(Subscriber) | /從Subscriber中隨機取50個值 |
5 | =now() | /當前時間 |
6 | =A1.find@k(A4) | /使用A.find@k()函數對A4進行批量鍵值查找 |
7 | =interval@ms(A5,now()) | /查詢花費的時間 |
代碼1.1.4
要注意的是,在使用A.find()函數時,需事先建立主鍵,否則會報“缺少主鍵”的錯誤。
利用主鍵值查找的函數,可以有效地提升計算性能,是由于在序表中為主鍵建立索引表。在代碼1.1.4中,未建立索引時,平均查詢時間在1400毫秒左右;建立索引后,查詢平均耗時不到1毫秒。
序表中的數據量越大,需要查找的次數越多,對效率的提升就越明顯。
當查詢條件對應多個鍵時,示例代碼如下:
A | B | |
1 | =file("btx/voiceBill.btx").import@b() | /讀取文件中的數據,作為序表 |
2 | >A1.keys(Subscriber,isLocal) | /設置Subscriber,isLocal為主鍵 |
3 | >A1.index() | /建立索引 |
4 | =[[13800000002,"F"],[13802568478,"F"]] | /鍵有多個,因此組成序列 |
5 | =now() | /當前時間 |
6 | =A1.find@k(A4) | /使用A.find@k()函數對A4進行批量鍵值查找 |
7 | =interval@ms(A5,now()) | /查詢花費的時間 |
代碼1.1.5
switch/join函數同樣需要根據主鍵值在序表中查找記錄,使用時會對維表自動建立索引。若在多線程fork函數之前沒有對相應維表建立索引,就會在每個線程中都自動為該維表建立一個索引,執行過程中會消耗更多內存,這樣有可能會造成內存溢出,如圖1.1.1.2,要注意避免,較好的處理方式可以參考圖?1.1.3。
圖?1.1.2 fork中的每個線程都自動建立了索引導致內存溢出
圖?1.1.3 fork執行前,先對維表建立索引
?
對有序的集文件進行查找,可以使用f.iselect()函數實現二分查找,該函數也支持批量查找,下面是個基于集文件使用f.iselect()批量查找的例子:
A | |
1 | =5000000.new(13800000000+~:Subscriber,datetime(1547123785410+rand(864000000)):Time,rand(3600)+1:Length,rands("TF",1):isLocal) |
2 | =file("btx/voiceBill@z.btx").export@z(A1) |
代碼1.2.1
代碼1.2.1,建立集文件voiceBill@z.btx。顯然,Subscriber是有序的。
A | B | |
1 | =file("btx/voiceBill@z.btx") | |
2 | =50.(13800000000+rand(5000000)+1).id() | /從500萬個Subscriber中隨機找50個,去重并排序 |
3 | =now() | /當前時間 |
4 | =A1.iselect@b(A2,Subscriber) | /對集文件使用f.iselect()二分查找函數進行批量查找 |
5 | =A4.fetch() | |
6 | =interval@ms(A3,now()) | /查詢花費的時間 |
代碼1.2.2
代碼1.2.2,因為f.iselect()是個二分查找函數,所以需要注意代碼中的A2作為查詢序列,與集文件的編號一樣,都需要有序。還要注意,這里的選項@b不是二分法的意思,而是讀取通過f.export()函數導出的集文件。該集文件導出時,注意需要使用選項@z,否則在使用f.iselect ()對集文件進行查詢時會報錯。
假設數據總量為N,使用二分法進行查找的時間復雜度為logN(以 2 為底),當數據量越大,性能提升也就越明顯。
?
組表也有類似序表的T.find()和T.find@k()函數,可以高效地實現鍵值查找。適合于在大維表中找出少量記錄的場景。我們來看這樣一個例子:
A | B | |
1 | =file("ctx/voiceBill.ctx").create(#Subscriber,Time,Length,isLocal) | |
2 | for 5000 | =to((A2-1)*10000+1,A2*10000).new(13800000000+~:Subscriber,datetime(1547123785410+rand(864000000)):Time,rand(3600)+1:Length,rands("TF",1):isLocal) |
3 | =A1.append(B2.cursor()) |
代碼1.3.1
代碼1.3.1,建立組表文件voiceBill.ctx,其中Subscriber是該組表的維。
A | B | |
1 | =file("ctx/voiceBill.ctx").create() | /打開組表 |
2 | =13801701672 | /組表數據其中的一個Subscriber值 |
3 | =now() | /當前時間 |
4 | =A1.cursor().select(Subscriber==A2).fetch() | /使用cs.select()對組表進行查詢 |
5 | =interval@ms(A3,now()) | /查詢花費的時間 |
代碼1.3.2
代碼1.3.2,對組表使用cs.select()函數進行查詢,耗時為:13855毫秒。
A | B | |
1 | =file("ctx/voiceBill.ctx").create() | /打開組表 |
2 | =13801701672 | /組表數據其中的一個Subscriber值 |
3 | =now() | /當前時間 |
4 | =A1.find(A2) | /使用T.find()對組表進行查詢 |
5 | =interval@ms(A3,now()) | /查詢花費的時間 |
代碼1.3.3
代碼1.3.3,對組表使用T.find()函數進行查詢,耗時為:77毫秒。
對比可見:對于有維的組表,可以使用類似序表的T.find()函數,進行單個或者批量鍵值的查詢,其查詢效率遠高于從篩選后的游標中取數。
組表上可以建立三種索引,每種索引針對的情況也不同,分別為:
1、? hash索引,適合單值查找,比如枚舉類型;
2、? 排序索引,適合區間查找,比如數字、日期、時間類型;
3、? 全文索引,用于模糊查詢,比如字符串類型。
下面我們來建立一個組表,使其數據類型覆蓋以上三種索引,如下:
A | B | |
1 | =file("ctx/voiceBillDetail.ctx").create(#Subscriber,Time,Length,isLocal,City,Company) | |
2 | =file("info/city_en.txt").import@i() | =A2.len() |
3 | for 5000 | =to((A3-1)*10000+1,A3*10000).new(13800000000+~:Subscriber,datetime(1547123785410+rand(864000000)):Time,rand(3600)+1:Length,rands("TF",1):isLocal,A2(rand(B2)+1):City,rands("ABCDEFGHIJKLMNOPQRSTUVWXYZ",14)+"? Co. Ltd":Company) |
4 | =A1.append(B3.cursor()) |
代碼2.1
代碼2.1建立的組表,前十條記錄如下:
圖2.1
A | B | |
1 | =file("ctx/voiceBillDetail.ctx").create() | /打開組表 |
2 | =A1.index(subscriber_idx;Subscriber) | /用戶手機號碼,數值用排序索引 |
3 | =A1.index(time_idx;Time) | /通話開始時間,數值用排序索引 |
4 | =A1.index(length_idx;Length) | /通話時長,數值用排序索引 |
5 | =A1.index(city_idx:1;City) | /城市,枚舉用hash索引 |
6 | =A1.index@w(company_idx;Company) | /公司,字串用全文索引 |
代碼2.2
代碼2.2,根據每列數據類型的特點,建立不同類型的索引。建立好的索引和組表文件如圖2.2:
圖2.2
集算器能自動識別條件找到合適的索引,等值和區間都可以,like(“A*”)式的也支持。我們來看下效果:
A | B | |
1 | =file("ctx/voiceBillDetail.ctx").create() | /打開組表 |
2 | =now() | /當前時間 |
3 | =A1.icursor(;Subscriber==13834750766,subscriber_idx).fetch() | /icursor查詢 |
4 | =interval@ms(A2,now()) | /查詢耗時 |
代碼2.3
A | B | |
1 | =file("ctx/voiceBillDetail.ctx").create() | /打開組表 |
2 | =now() | /當前時間 |
3 | =A1.icursor(;Subscriber==13834750766).fetch() | /icursor查詢 |
4 | =interval@ms(A2,now()) | /查詢耗時 |
代碼2.4
代碼2.3是沒有省略索引名稱的寫法,代碼2.4是省略索引名稱的寫法。兩者時間消耗基本相同,都是100毫秒左右。
A | B | |
1 | =file("ctx/voiceBillDetail.ctx").create() | /打開組表 |
2 | =now() | /當前時間 |
3 | =A1.cursor().select(Subscriber==13834750766).fetch() | /普通游標查詢 |
4 | =interval@ms(A2,now()) | /查詢耗時 |
代碼2.5
代碼2.5使用普通游標查詢同樣的記錄,查詢耗時則需要40秒左右。
?
A | B | |
1 | =file("ctx/voiceBillDetail.ctx").create() | /打開組表 |
2 | =now() | /當前時間 |
3 | =A1.icursor(;Subscriber>=13834750766 && ? Subscriber<=13834750780).fetch() | /icursor查詢 |
4 | =interval@ms(A2,now()) | /查詢耗時 |
代碼2.6
代碼2.6對Subscriber使用排序索引,進行區間查找,查詢耗時是70毫秒左右。
A | B | |
1 | =file("ctx/voiceBillDetail.ctx").create() | /打開組表 |
2 | =now() | /當前時間 |
3 | =A1.cursor().select(Subscriber>=13834750766 ? && Subscriber<=13834750780).fetch() | /普通游標查詢 |
4 | =interval@ms(A2,now()) | /查詢耗時 |
代碼2.7
代碼2.7使用普通游標查詢同樣條件的記錄,查詢耗時則需要40秒左右。
?
A | B | |
1 | =file("ctx/voiceBillDetail.ctx").create() | /打開組表 |
2 | =now() | /當前時間 |
3 | =A1.icursor(like(Company,"*ABCDE*")).fetch() | /icursor查詢 |
4 | =interval@ms(A2,now()) | /查詢耗時 |
代碼2.8
代碼2.8對Company使用全文索引,進行模糊查詢,查詢耗時是1500毫秒左右。
A | B | |
1 | =file("ctx/voiceBillDetail.ctx").create() | /打開組表 |
2 | =now() | /當前時間 |
3 | =A1.cursor().select(like(Company,"*ABCDE*")).fetch() | /普通游標查詢 |
4 | =interval@ms(A2,now()) | /查詢耗時 |
代碼2.9
代碼2.9使用普通游標查詢同樣條件的記錄,查詢耗時則需要40秒左右。
?
當數據規模更大時,例如:
A | B | |
1 | =file("ctx/employee.ctx") | |
2 | =A1.create(#id,name,sex,city,birthday,salary,level,height,weight,company) | |
3 | =file("info/ming_en_female.txt").import@i() | =A3.len() |
4 | =file("info/ming_en_male.txt").import@i() | =A4.len() |
5 | =file("info/xing_en.txt").import@i() | =A5.len() |
6 | =city=file("info/city_en.txt").import@i() | =A6.len() |
7 | =salary=20000 | /10000~30000 |
8 | =["one","two","three","four","five","six","seven","eight","nine","ten"] | =A8.len() |
9 | =height=50 | /160~210cm |
10 | =weight=50 | /50~100kg |
11 | =birthtime=946656 | /1970~2000 |
12 | for 10000 | =to((A12-1)*100000+1,A12*100000).new(~:id,if(rand(2)==0,A3(rand(B3)+1),A4(rand(B4)+1))+"?"+A5(rand(B5)+1):name,if(A3.pos(name.array("")(1)),"Female","Male"):sex,A6(rand(B6-1)+1):city,date(rand(birthtime)*long(1000000)):birthday,rand(salary)+10000:salary,A8(rand(B8-1)+1):level,rand(height)+160:height,rand(weight)+50:weight,if(rand(2)==0,A3(rand(B3)+1),A4(rand(B4)+1))+"&"+A5(rand(B5)+1)+" ? Co. Ltd":company) |
13 | =A2.append(B12.cursor()) |
代碼2.10
代碼2.10,建造了10億條結構如圖2.3的組表文件employee.ctx。
圖2.3
A | B | |
1 | =file("ctx/employee.ctx").create() | /打開組表 |
2 | =A1.index(id_idx;id) | /編號,數值用排序索引 |
3 | =A1.index@w(name_idx;name) | /姓名,字串用全文 |
4 | =A1.index(city_idx:1;city) | /城市,枚舉用hash索引 |
5 | =A1.index(birthday;birthday) | /生日,日期用排序索引 |
6 | =A1.index(salary_idx;salary) | /工資,數值用排序索引 |
7 | =A1.index(height_idx;height) | /身高,數值用排序索引 |
8 | =A1.index(weight_idx;weight) | /體重,數值用排序索引 |
9 | =A1.index@w(company;company) | /公司,字串用全文索引 |
代碼2.11
?
代碼2.11中,對大部分列建立了索引。組表與索引的各個文件如圖2.4。
圖2.4
多等值條件項&&時,可以分別為每個字段建立索引。集算器能夠快速在多個索引中用歸并算法計算交集。比如:
A | B | |
1 | =file("ctx/employee.ctx").create() | /打開組表 |
2 | =now() | /當前時間 |
3 | =A1.icursor(;city=="Casper" ? && salary==25716).fetch() | /icursor查詢多個等值條件 |
4 | =interval@ms(A2,now()) | /查詢耗時 |
代碼2.12
代碼2.12,查詢條件均為等值查詢,A3查出記錄數為324條,耗時31883毫秒。
但區間條件時不能再用歸并計算交集,集算器將只對其中一個條件使用索引,另一個條件使用遍歷計算,效果就會差,比如:
A | B | |
1 | =file("ctx/employee.ctx").create() | /打開組表 |
2 | =now() | /當前時間 |
3 | =A1.icursor(;height>208 && ? weight<70? && ? salary==21765).fetch() | /icursor查詢多個區間條件 |
4 | =interval@ms(A2,now()) | /查詢耗時 |
代碼2.13
代碼2.13,查詢條件均為區間條件,A3查出記錄數為389條,耗時70283毫秒。
?
組表索引提供了兩級緩存機制,可以用index@2或者index@3預先把索引的索引讀入內存,如果需要重復多次使用索引查找,則可以有效提高性能。
選項@2、@3的意思分別是將索引的第二、三級緩存先加載進內存。經過索引緩存的預處理,第一遍查詢時間也能達到查詢數百次后才能達到的極限值。@2相比@3緩存的內容少,效果相對差一點,但內存占用也更少。使用時需要程序員根據具體場景來權衡@2還是@3。
A | B | |
1 | =file("ctx/employee.ctx").create() | /打開組表 |
2 | =now() | /當前時間 |
3 | =A1.icursor(;city=="Casper" ? && salary==25716).fetch() | /icursor查詢 |
4 | =interval@ms(A2,now()) | /查詢耗時 |
代碼3.1
代碼3.1,基于代碼2.10建造的組表文件,不使用索引緩存,查詢耗時為31883毫秒。
A | B | |
1 | =file("ctx/employee.ctx").create() | /打開組表 |
2 | =A1.index@3(city_idx) | /使用第三級緩存 |
3 | =A1.index@3(salary_idx) | /使用第三級緩存 |
4 | =now() | /當前時間 |
5 | =A1.icursor(;city=="Casper" ? && salary==25716).fetch() | /icursor查詢 |
6 | =interval@ms(A4,now()) | /查詢耗時 |
代碼3.2
代碼3.2使用第三級索引緩存,查詢耗時為5225毫秒。
這里使用的是列存組表,列存采用了數據分塊并壓縮的算法,對于遍歷運算來講,訪問數據量會變小,也就會具有更好的性能。但對于基于索引隨機取數的場景,由于要有額外的解壓過程,而且每次取數都會針對整個分塊,運算復雜度會高很多。因此,從原理上分析,這時候的性能應當會比行存要差。將組表轉為行存后,查詢耗時僅為1592毫秒。
?
索引緩存在并行時可以復用,如下:
A | B | |
1 | =file("ctx/employee.ctx").create() | /打開組表 |
2 | =A1.index@3(city_idx) | /使用第三級緩存 |
3 | =A1.index@3(salary_idx) | /使用第三級緩存 |
4 | =now() | /當前時間 |
5 | fork [22222,23333,24444,25555] | =A1.icursor(;city=="Casper" ? && salary==A5).fetch() |
6 | return B5 | |
7 | =A5.conj() | /合并并行結果 |
8 | =interval@ms(A4,now()) | /并行查詢耗時 |
代碼3.3
代碼3.3,并行時,A5的每個線程中都可以使用A2、A3中建立的第三級索引緩存,最終查詢耗時為21376毫秒。
?
組表的行存和列存形式都支持索引,列存索引查找比行存性能差,返回結果集較少時差異不明顯,大量返回時會有明顯劣勢,在設計存儲方案時要權衡。
A | B | |
1 | 1234567890qwertyuiopasdfghjklzxcvbnm | |
2 | =file("id_600m.ctx") | |
3 | =A2.create(#id,data) | |
4 | for 6000 | =to((A4-1)*100000+1,A4*100000).new(~+1000000000000:id,rands(A1,180):data) |
5 | =A3.append(B4.cursor()) |
代碼4.1
代碼4.1建立組表文件id_600m.ctx,結構為(#id,data) ,包含6億條記錄,其中:
A1:包含 26 個英文字母和 10 個阿拉伯數字的字符串。
A2、A3:建立結構為 (id,data) 的組表文件,使用列式存儲方式。
A4:循環 6000 次,循環體B4、B5,每次生成 10 萬條對應結構的記錄,并追加到組表文件。
執行后,生成組表文件:id_600m.ctx
?
A | |
1 | =file("id_600m.ctx") |
2 | =A1.create().index(id_idx;id) |
代碼4.2
代碼4.2為組表id列建立索引。
執行后,生成組表的索引文件:id_600m.ctx__id_idx。
?
列存組表生成時 create() 函數加上 @r 選項,即可變為生成行存組表,其余代碼無異,這里不再舉例,當返回數據量較大時:
?
A | B | |
1 | =10000.(rand(600000000)+1000000000001).id() | /根據數據特點,從六億中隨機取一萬個待查詢的測試id鍵值并去重 |
2 | =file("id_600m.ctx").create() | /打開列存組表 |
3 | =now() | /當前時間 |
4 | =A2.icursor(;A1.contain(id),id_idx).fetch() | /icursor查詢A1中對應的id鍵值 |
5 | =interval@ms(A3,now()) | /列存查詢耗時 |
6 | =file("id_600m@r.ctx").create() | /打開行存組表 |
7 | =now() | /當前時間 |
8 | =A6.icursor(;A1.contain(id),id_idx).fetch() | /icursor查詢A1中對應的id鍵值 |
9 | =interval@ms(A7,now()) | /行存查詢耗時 |
代碼4.3
代碼4.3中,列存查詢耗時和行存查詢耗時,也就是A5和A9的值分別為205270和82800毫秒。
?
組表支持一種帶值索引,即把查找字段也寫入索引,這樣可以不再訪問原組表即返回結果。但存儲空間會占用較多。
基于代碼4.1的列存組表文件id_600m.ctx。
?
A | |
1 | =file("id_600m.ctx") |
2 | =A1.create().index(id_data_idx;id;data) |
代碼4.4
代碼4.4為組表id列建立索引,在對組表建立索引時,當 index 函數有數據列名參數,如本例 A2 中的 data,就會在建索引時把數據列 data 復制進索引。當有多個數據列時,可以寫為:index(id_idx;id;data1,data2,…)。
因為在索引中做了冗余,索引文件也自然會較大,本文中測試的列存組表和索引冗余后的文件大小為:
類型 | 文件名 | 大小 |
組表 | id_600m.ctx | 105G |
索引 | id_600m.ctx__id_data_idx | 112G |
?
當數據復制進索引后,實際上讀取時不再訪問原數據文件了。
從 6 億條數據總量中取 1 萬條批量隨機鍵值,完整的測試結果對比:
耗時(毫秒) | |||||
單線程 | 多線程(10 線程) | ||||
Oracle | 行存索引 | 冗余索引 | Oracle | 行存索引 | 冗余索引 |
117322 | 20745 | 19873 | 39549 | 10975 | 9561 |
?
?
組表索引能夠識別出contain式條件,支持批量等值查找。
A | B | |
1 | 1234567890qwertyuiopasdfghjklzxcvbnm | |
2 | =file("id_600m.ctx") | |
3 | =A2.create@r(#id,data) | |
4 | for 6000 | =to((A4-1)*100000+1,A4*100000).new(~+1000000000000:id,rands(A1,180):data) |
5 | =A3.append(B4.cursor()) |
代碼5.1
代碼5.1建立組表文件id_600m.ctx,結構為(#id,data) ,包含6億條記錄,其中:
A1:包含 26 個英文字母和 10 個阿拉伯數字的字符串。
A2、A3:建立結構為 (id,data) 的組表文件,@r 選項表示使用行式存儲方式。
A4:循環 6000 次,循環體B4、B5,每次生成 10 萬條對應結構的記錄,并追加到組表文件。
執行后,生成組表文件:id_600m.ctx。
?
A | |
1 | =file("id_600m.ctx ? ?") |
2 | =A1.create().index(id_idx;id) |
代碼5.2
代碼5.2為組表id列建立索引。
執行后,生成組表的索引文件:id_600m.ctx__id_idx
A | B | |
1 | =file("id_600m.ctx").create() | /打開組表 |
2 | =now() | /當前時間 |
3 | =A1.index@3(id_idx) | /使用index@3加載索引緩存 |
4 | =interval@ms(A2,now()) | /加載索引緩存的時間 |
5 | =10000.(1000000000000+(rand(600000000)+1)).sort() | /隨機取一些鍵值并排序 |
6 | =now() | /當前時間 |
7 | =A1.icursor(A5.contain(id),id_idx).fetch() | /使用icursor函數進行批量鍵值查詢 |
8 | =interval@ms(A6,now()) | /查詢耗時 |
代碼5.3
代碼5.3,在組表的 icursor()這個函數中,使用索引 id_idx,以條件 A2.contain(id) 來過濾組表。集算器會自動識別出 A2.contain(id) 這個條件可以使用索引,并會自動將 A2 的內容排序后從前向后查找。
進階使用
使用排序索引多線程查找時,按鍵值排序分組后扔給多個線程去查詢,避免兩個線程中有交叉內容。同時,還可以設計成多個組表,把鍵值能平均分配到多個組表上并行查找。
所謂多線程并行,就是把數據分成 N 份,用 N 個線程查詢。但如果只是隨意地將數據分成 N 份,很可能無法真正地提高性能。因為將要查詢的鍵值集是未知的,所以理論上也無法確保希望查找的數據能夠均勻分布在每一份組表文件中。比較好的處理方式是先觀察鍵值集的特征,從而盡可能地進行數據的均勻拆分。
如果鍵值數據有比較明顯的業務特征,我們可以考慮按照實際業務場景使用日期、部門之類的字段來處理文件拆分。如:將屬于部門 A 的 1000 條記錄均分在 10 個文件中,每個文件就有 100 條記錄。在利用多線程查詢屬于部門 A 的記錄時,每個線程就會從各自對應的文件中取數相應的這 100 條記錄了。
下面我們來看個實際的例子,已有數據文件multi_source.txt的結構如下:
字段名稱 | 類型 | 說明 |
type | string | 可枚舉 |
Id | long | 每個枚舉類型的 id 都從 1 開始自增 |
data | string | 需要獲取的數據 |
其中 type 和 id 兩個字段作為聯合主鍵確定一條記錄,其中部分數據如下:
?
A | |
1 | =["type_a",……,"type_z","type_1",……,"type_9","type_0"] |
2 | =A1.new(#:tid,~:type) |
3 | =file("multi_source.txt") |
4 | =A3.cursor@t() |
5 | =A4.switch(type,A2:type) |
6 | =A4.new(1000000000000+type.tid*long(1000000000)+id:nid,data) |
7 | =N.(file("nid_"+string(~-1)+"_T.ctx").create(#nid,data)) |
8 | =N.(eval("channel(A4).select(nid%N=="+string(~-1)+").attach(A7("+string(~)+").append(~.cursor()))")) |
9 | for A6,500000 |
代碼5.4
代碼5.4詳解:
A1:type 的枚舉值組成的序列。在實際情況中,枚舉列表可能來自文件或者數據庫數據源。。
A2:給枚舉值序列中每個 type 一個 tid。為后續的數字化主鍵合并做準備。
A3~A6:從 multi_source.txt 文件中獲取數據,并按照 A2 中的對應關系,把 type 列的枚舉串變成數字,然后將 type 和 id 進行合并后,生成新的主鍵 nid。
A7:使用循環函數,創建名為“鍵值名 _ 鍵值取 N 的余數 _T.ctx”的組表文件,其結構同為 (#nid,data)。
A8:用循環函數將游標數據分別追加到 N 個原組表上。比如當 N=1 時,拼出的 eval 函數參數為:channel(A4).select(nid%4==0).attach(A7(1).append(~.cursor()))。意思是對游標 A4 創建管道,將管道中記錄按鍵值 nid 取 4 的余數,將余數值等于 0 的記錄過濾出來。attach 是對當前管道的附加運算,表示取和當前余數值對應的原組表,將當前管道中篩選過濾出的記錄,以游標記錄的方式追加到 A7(1),即第 1 個組表。
A9:循環游標 A6,每次獲取 50 萬條記錄,直至 A6 游標中的數據取完。
執行后,產出 4(這時例子取 N=4)個獨立的組表文件:
?
A | B | |
1 | fork directory@p("nid*T.ctx") | =file(A1).create().index(nid_idx;nid;data) |
代碼5.5
代碼5.5,創建索引過程詳解:
A1:列出滿足 nid*T.ctx 的文件名(這里 * 為通配符),這里 @p 選項代表需要返回帶有完整路徑信息的文件名。使用 fork 執行多線程時,需要注意環境中的并行限制數是否設置合理。這里用了 4 個線程,設計器中對應的設置如下:
B1:每個線程為各個組表建立對應的索引文件,最終結果如下:
?
A | B | |
1 | =file("keys.txt").import@i() | |
2 | =A1.group(~%N) | |
3 | fork N.(~-1),A2 | =A3(2) |
4 | =file("nid_"/A3(1)/"_T.ctx").create().icursor(;B3.contain(nid),nid_idx) | |
5 | return B4 | |
6 | =A3.conjx() | |
7 | =file("result_nid.txt").export@t(A6) |
代碼5.6
代碼5.6,查詢過程詳解:
A1:從 keys.txt 獲取查詢鍵值序列,因為只有一列結果,使用 @i 選項,將結果返回成序列:
A2:把 A1 的序列按 4 的余數進行等值分組:
A3、B3~B5:用 fork 函數,按等值分組后的鍵值對各個組表分別并行查詢。這里的 fork 后面分別寫了兩個參數,第一個是循環函數 N.(~-1),第二個是 A2。在接下來的 B3、B4 中分別使用 A3(2) 和 A3(1) 來獲取 fork 后面這兩個對應順序的參數,B4:對組表文件進行根據 B3 中的鍵值集進行數據篩選,B5:返回游標。由于 A3 中是多個線程返回的游標序列,所以 A6 中需要使用 conjx 對多個游標進行縱向連接。
A6~A7:將多個線程返回的游標進行縱向連接后,導出游標記錄至文本文件,前幾行內容如下。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。