溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

性能優(yōu)化技巧 - 查找

發(fā)布時(shí)間:2020-07-06 14:18:13 來(lái)源:網(wǎng)絡(luò) 閱讀:314 作者:raqsoft 欄目:大數(shù)據(jù)

日常生活中,我們會(huì)遇到各種各樣的數(shù)據(jù),小到公司通訊錄,大到互聯(lián)網(wǎng)用戶行為分析。在進(jìn)行數(shù)據(jù)分析處理的過(guò)程中,查詢是必不可少的環(huán)節(jié),如何更加高效地進(jìn)行數(shù)據(jù)查詢。

SPL為用戶提供了強(qiáng)大的索引機(jī)制以及針對(duì)不同場(chǎng)景中各對(duì)象的查詢函數(shù),善加運(yùn)用,可以顯著提高查詢性能。

1?鍵值查找

1.1?序表

我們先建立一個(gè)份“通話記錄”的模擬數(shù)據(jù),通過(guò)這份數(shù)據(jù),來(lái)比較一下不同查詢函數(shù)對(duì)序表查詢性能的影響。建立模擬數(shù)據(jù)的代碼如下:


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

其中部分?jǐn)?shù)據(jù)如下:

性能優(yōu)化技巧 - 查找

圖1.1.1

對(duì)序表進(jìn)行查詢,通常我們會(huì)想到使用A.select()函數(shù)。我們來(lái)看一下使用該函數(shù)的效果:


AB
1=file("btx/voiceBill.btx").import@b()/讀取文件中的數(shù)據(jù),作為序表
2=now()/當(dāng)前時(shí)間
3=A1.select(Subscriber==13800263524)/使用A.select()函數(shù)進(jìn)行查詢
4=interval@ms(A2,now())/查詢耗時(shí)

代碼1.1.2

查詢耗時(shí)為80毫秒。

對(duì)序表的鍵值進(jìn)行查詢時(shí),可以利用A.find()函數(shù)進(jìn)行查詢。示例代碼如下:


AB
1=file("btx/voiceBill.btx").import@b()/讀取文件中的數(shù)據(jù),作為序表
2>A1.keys(Subscriber)/設(shè)置Subscriber為主鍵
3>A1.index()/建立索引
4=now()/當(dāng)前時(shí)間
5=A1.find(13800263524)/使用A.find()函數(shù)查找
6=interval@ms(A4,now())/查詢花費(fèi)的時(shí)間

代碼1.1.3

查詢耗時(shí)為1毫秒。

這是因?yàn)樵诩闫鞯男虮碇?,可以指定某個(gè)或某些字段作為主鍵,基于主鍵的查找可以使用專門的函數(shù)。比如代碼1.1.3中A5的find函數(shù),不僅能簡(jiǎn)化書寫,更能有效地提高計(jì)算性能。

當(dāng)鍵值較多時(shí),我們使用函數(shù)A.find@k ()進(jìn)行批量鍵值查找。示例代碼如下:


AB
1=file("btx/voiceBill.btx").import@b()/讀取文件中的數(shù)據(jù),作為序表
2>A1.keys(Subscriber)/設(shè)置Subscriber為主鍵
3>A1.index()/建立索引
4=A1(50.(rand(A1.len())+1)).(Subscriber)/從Subscriber中隨機(jī)取50個(gè)值
5=now()/當(dāng)前時(shí)間
6=A1.find@k(A4)/使用A.find@k()函數(shù)對(duì)A4進(jìn)行批量鍵值查找
7=interval@ms(A5,now())/查詢花費(fèi)的時(shí)間

代碼1.1.4

要注意的是,在使用A.find()函數(shù)時(shí),需事先建立主鍵,否則會(huì)報(bào)“缺少主鍵”的錯(cuò)誤。

利用主鍵值查找的函數(shù),可以有效地提升計(jì)算性能,是由于在序表中為主鍵建立索引表。在代碼1.1.4中,未建立索引時(shí),平均查詢時(shí)間在1400毫秒左右;建立索引后,查詢平均耗時(shí)不到1毫秒。

序表中的數(shù)據(jù)量越大,需要查找的次數(shù)越多,對(duì)效率的提升就越明顯。

當(dāng)查詢條件對(duì)應(yīng)多個(gè)鍵時(shí),示例代碼如下:


AB
1=file("btx/voiceBill.btx").import@b()/讀取文件中的數(shù)據(jù),作為序表
2>A1.keys(Subscriber,isLocal)/設(shè)置Subscriber,isLocal為主鍵
3>A1.index()/建立索引
4=[[13800000002,"F"],[13802568478,"F"]]/鍵有多個(gè),因此組成序列
5=now()/當(dāng)前時(shí)間
6=A1.find@k(A4)/使用A.find@k()函數(shù)對(duì)A4進(jìn)行批量鍵值查找
7=interval@ms(A5,now())/查詢花費(fèi)的時(shí)間

代碼1.1.5

switch/join函數(shù)同樣需要根據(jù)主鍵值在序表中查找記錄,使用時(shí)會(huì)對(duì)維表自動(dòng)建立索引。若在多線程fork函數(shù)之前沒有對(duì)相應(yīng)維表建立索引,就會(huì)在每個(gè)線程中都自動(dòng)為該維表建立一個(gè)索引,執(zhí)行過(guò)程中會(huì)消耗更多內(nèi)存,這樣有可能會(huì)造成內(nèi)存溢出,如圖1.1.1.2,要注意避免,較好的處理方式可以參考圖?1.1.3。

性能優(yōu)化技巧 - 查找

圖?1.1.2 fork中的每個(gè)線程都自動(dòng)建立了索引導(dǎo)致內(nèi)存溢出

性能優(yōu)化技巧 - 查找

圖?1.1.3 fork執(zhí)行前,先對(duì)維表建立索引

?

1.2?集文件

對(duì)有序的集文件進(jìn)行查找,可以使用f.iselect()函數(shù)實(shí)現(xiàn)二分查找,該函數(shù)也支持批量查找,下面是個(gè)基于集文件使用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是有序的。


AB
1=file("btx/voiceBill@z.btx")
2=50.(13800000000+rand(5000000)+1).id()/從500萬(wàn)個(gè)Subscriber中隨機(jī)找50個(gè),去重并排序
3=now()/當(dāng)前時(shí)間
4=A1.iselect@b(A2,Subscriber)/對(duì)集文件使用f.iselect()二分查找函數(shù)進(jìn)行批量查找
5=A4.fetch()
6=interval@ms(A3,now())/查詢花費(fèi)的時(shí)間

代碼1.2.2

代碼1.2.2,因?yàn)閒.iselect()是個(gè)二分查找函數(shù),所以需要注意代碼中的A2作為查詢序列,與集文件的編號(hào)一樣,都需要有序。還要注意,這里的選項(xiàng)@b不是二分法的意思,而是讀取通過(guò)f.export()函數(shù)導(dǎo)出的集文件。該集文件導(dǎo)出時(shí),注意需要使用選項(xiàng)@z,否則在使用f.iselect ()對(duì)集文件進(jìn)行查詢時(shí)會(huì)報(bào)錯(cuò)。

假設(shè)數(shù)據(jù)總量為N,使用二分法進(jìn)行查找的時(shí)間復(fù)雜度為logN(以 2 為底),當(dāng)數(shù)據(jù)量越大,性能提升也就越明顯。

?

1.3?組表

組表也有類似序表的T.find()和T.find@k()函數(shù),可以高效地實(shí)現(xiàn)鍵值查找。適合于在大維表中找出少量記錄的場(chǎng)景。我們來(lái)看這樣一個(gè)例子:


AB
1=file("ctx/voiceBill.ctx").create(#Subscriber,Time,Length,isLocal)
2for 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是該組表的維。


AB
1=file("ctx/voiceBill.ctx").create()/打開組表
2=13801701672/組表數(shù)據(jù)其中的一個(gè)Subscriber值
3=now()/當(dāng)前時(shí)間
4=A1.cursor().select(Subscriber==A2).fetch()/使用cs.select()對(duì)組表進(jìn)行查詢
5=interval@ms(A3,now())/查詢花費(fèi)的時(shí)間

代碼1.3.2

代碼1.3.2,對(duì)組表使用cs.select()函數(shù)進(jìn)行查詢,耗時(shí)為:13855毫秒。


AB
1=file("ctx/voiceBill.ctx").create()/打開組表
2=13801701672/組表數(shù)據(jù)其中的一個(gè)Subscriber值
3=now()/當(dāng)前時(shí)間
4=A1.find(A2)/使用T.find()對(duì)組表進(jìn)行查詢
5=interval@ms(A3,now())/查詢花費(fèi)的時(shí)間

代碼1.3.3

代碼1.3.3,對(duì)組表使用T.find()函數(shù)進(jìn)行查詢,耗時(shí)為:77毫秒。

對(duì)比可見:對(duì)于有維的組表,可以使用類似序表的T.find()函數(shù),進(jìn)行單個(gè)或者批量鍵值的查詢,其查詢效率遠(yuǎn)高于從篩選后的游標(biāo)中取數(shù)。

2 索引查找

組表上可以建立三種索引,每種索引針對(duì)的情況也不同,分別為:

1、? hash索引,適合單值查找,比如枚舉類型;

2、? 排序索引,適合區(qū)間查找,比如數(shù)字、日期、時(shí)間類型;

3、? 全文索引,用于模糊查詢,比如字符串類型。

下面我們來(lái)建立一個(gè)組表,使其數(shù)據(jù)類型覆蓋以上三種索引,如下:


AB
1=file("ctx/voiceBillDetail.ctx").create(#Subscriber,Time,Length,isLocal,City,Company)
2=file("info/city_en.txt").import@i()=A2.len()
3for 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建立的組表,前十條記錄如下:

性能優(yōu)化技巧 - 查找

圖2.1


AB
1=file("ctx/voiceBillDetail.ctx").create()/打開組表
2=A1.index(subscriber_idx;Subscriber)/用戶手機(jī)號(hào)碼,數(shù)值用排序索引
3=A1.index(time_idx;Time)/通話開始時(shí)間,數(shù)值用排序索引
4=A1.index(length_idx;Length)/通話時(shí)長(zhǎng),數(shù)值用排序索引
5=A1.index(city_idx:1;City)/城市,枚舉用hash索引
6=A1.index@w(company_idx;Company)/公司,字串用全文索引

代碼2.2

代碼2.2,根據(jù)每列數(shù)據(jù)類型的特點(diǎn),建立不同類型的索引。建立好的索引和組表文件如圖2.2:

性能優(yōu)化技巧 - 查找

圖2.2

集算器能自動(dòng)識(shí)別條件找到合適的索引,等值和區(qū)間都可以,like(“A*”)式的也支持。我們來(lái)看下效果:

等值查找

AB
1=file("ctx/voiceBillDetail.ctx").create()/打開組表
2=now()/當(dāng)前時(shí)間
3=A1.icursor(;Subscriber==13834750766,subscriber_idx).fetch()/icursor查詢
4=interval@ms(A2,now())/查詢耗時(shí)

代碼2.3


AB
1=file("ctx/voiceBillDetail.ctx").create()/打開組表
2=now()/當(dāng)前時(shí)間
3=A1.icursor(;Subscriber==13834750766).fetch()/icursor查詢
4=interval@ms(A2,now())/查詢耗時(shí)

代碼2.4

代碼2.3是沒有省略索引名稱的寫法,代碼2.4是省略索引名稱的寫法。兩者時(shí)間消耗基本相同,都是100毫秒左右。


AB
1=file("ctx/voiceBillDetail.ctx").create()/打開組表
2=now()/當(dāng)前時(shí)間
3=A1.cursor().select(Subscriber==13834750766).fetch()/普通游標(biāo)查詢
4=interval@ms(A2,now())/查詢耗時(shí)

代碼2.5

代碼2.5使用普通游標(biāo)查詢同樣的記錄,查詢耗時(shí)則需要40秒左右。

?

區(qū)間查找

AB
1=file("ctx/voiceBillDetail.ctx").create()/打開組表
2=now()/當(dāng)前時(shí)間
3=A1.icursor(;Subscriber>=13834750766 && ? Subscriber<=13834750780).fetch()/icursor查詢
4=interval@ms(A2,now())/查詢耗時(shí)

代碼2.6

代碼2.6對(duì)Subscriber使用排序索引,進(jìn)行區(qū)間查找,查詢耗時(shí)是70毫秒左右。


AB
1=file("ctx/voiceBillDetail.ctx").create()/打開組表
2=now()/當(dāng)前時(shí)間
3=A1.cursor().select(Subscriber>=13834750766 ? && Subscriber<=13834750780).fetch()/普通游標(biāo)查詢
4=interval@ms(A2,now())/查詢耗時(shí)

代碼2.7

代碼2.7使用普通游標(biāo)查詢同樣條件的記錄,查詢耗時(shí)則需要40秒左右。

?

模糊查找

AB
1=file("ctx/voiceBillDetail.ctx").create()/打開組表
2=now()/當(dāng)前時(shí)間
3=A1.icursor(like(Company,"*ABCDE*")).fetch()/icursor查詢
4=interval@ms(A2,now())/查詢耗時(shí)

代碼2.8

代碼2.8對(duì)Company使用全文索引,進(jìn)行模糊查詢,查詢耗時(shí)是1500毫秒左右。


AB
1=file("ctx/voiceBillDetail.ctx").create()/打開組表
2=now()/當(dāng)前時(shí)間
3=A1.cursor().select(like(Company,"*ABCDE*")).fetch()/普通游標(biāo)查詢
4=interval@ms(A2,now())/查詢耗時(shí)

代碼2.9

代碼2.9使用普通游標(biāo)查詢同樣條件的記錄,查詢耗時(shí)則需要40秒左右。

?

當(dāng)數(shù)據(jù)規(guī)模更大時(shí),例如:


AB
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
12for 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億條結(jié)構(gòu)如圖2.3的組表文件employee.ctx。

性能優(yōu)化技巧 - 查找

圖2.3


AB
1=file("ctx/employee.ctx").create()/打開組表
2=A1.index(id_idx;id)/編號(hào),數(shù)值用排序索引
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)/工資,數(shù)值用排序索引
7=A1.index(height_idx;height)/身高,數(shù)值用排序索引
8=A1.index(weight_idx;weight)/體重,數(shù)值用排序索引
9=A1.index@w(company;company)/公司,字串用全文索引

代碼2.11

?

代碼2.11中,對(duì)大部分列建立了索引。組表與索引的各個(gè)文件如圖2.4。

性能優(yōu)化技巧 - 查找

圖2.4

多等值條件項(xiàng)&&時(shí),可以分別為每個(gè)字段建立索引。集算器能夠快速在多個(gè)索引中用歸并算法計(jì)算交集。比如:


AB
1=file("ctx/employee.ctx").create()/打開組表
2=now()/當(dāng)前時(shí)間
3=A1.icursor(;city=="Casper" ? && salary==25716).fetch()/icursor查詢多個(gè)等值條件
4=interval@ms(A2,now())/查詢耗時(shí)

代碼2.12

代碼2.12,查詢條件均為等值查詢,A3查出記錄數(shù)為324條,耗時(shí)31883毫秒。

但區(qū)間條件時(shí)不能再用歸并計(jì)算交集,集算器將只對(duì)其中一個(gè)條件使用索引,另一個(gè)條件使用遍歷計(jì)算,效果就會(huì)差,比如:


AB
1=file("ctx/employee.ctx").create()/打開組表
2=now()/當(dāng)前時(shí)間
3=A1.icursor(;height>208 && ? weight<70? && ? salary==21765).fetch()/icursor查詢多個(gè)區(qū)間條件
4=interval@ms(A2,now())/查詢耗時(shí)

代碼2.13

代碼2.13,查詢條件均為區(qū)間條件,A3查出記錄數(shù)為389條,耗時(shí)70283毫秒。

?

3 索引緩存

組表索引提供了兩級(jí)緩存機(jī)制,可以用index@2或者index@3預(yù)先把索引的索引讀入內(nèi)存,如果需要重復(fù)多次使用索引查找,則可以有效提高性能。

選項(xiàng)@2、@3的意思分別是將索引的第二、三級(jí)緩存先加載進(jìn)內(nèi)存。經(jīng)過(guò)索引緩存的預(yù)處理,第一遍查詢時(shí)間也能達(dá)到查詢數(shù)百次后才能達(dá)到的極限值。@2相比@3緩存的內(nèi)容少,效果相對(duì)差一點(diǎn),但內(nèi)存占用也更少。使用時(shí)需要程序員根據(jù)具體場(chǎng)景來(lái)權(quán)衡@2還是@3。


AB
1=file("ctx/employee.ctx").create()/打開組表
2=now()/當(dāng)前時(shí)間
3=A1.icursor(;city=="Casper" ? && salary==25716).fetch()/icursor查詢
4=interval@ms(A2,now())/查詢耗時(shí)

代碼3.1

代碼3.1,基于代碼2.10建造的組表文件,不使用索引緩存,查詢耗時(shí)為31883毫秒。


AB
1=file("ctx/employee.ctx").create()/打開組表
2=A1.index@3(city_idx)/使用第三級(jí)緩存
3=A1.index@3(salary_idx)/使用第三級(jí)緩存
4=now()/當(dāng)前時(shí)間
5=A1.icursor(;city=="Casper" ? && salary==25716).fetch()/icursor查詢
6=interval@ms(A4,now())/查詢耗時(shí)

代碼3.2

代碼3.2使用第三級(jí)索引緩存,查詢耗時(shí)為5225毫秒。

這里使用的是列存組表,列存采用了數(shù)據(jù)分塊并壓縮的算法,對(duì)于遍歷運(yùn)算來(lái)講,訪問數(shù)據(jù)量會(huì)變小,也就會(huì)具有更好的性能。但對(duì)于基于索引隨機(jī)取數(shù)的場(chǎng)景,由于要有額外的解壓過(guò)程,而且每次取數(shù)都會(huì)針對(duì)整個(gè)分塊,運(yùn)算復(fù)雜度會(huì)高很多。因此,從原理上分析,這時(shí)候的性能應(yīng)當(dāng)會(huì)比行存要差。將組表轉(zhuǎn)為行存后,查詢耗時(shí)僅為1592毫秒。

?

索引緩存在并行時(shí)可以復(fù)用,如下:


AB
1=file("ctx/employee.ctx").create()/打開組表
2=A1.index@3(city_idx)/使用第三級(jí)緩存
3=A1.index@3(salary_idx)/使用第三級(jí)緩存
4=now()/當(dāng)前時(shí)間
5fork [22222,23333,24444,25555]=A1.icursor(;city=="Casper" ? && salary==A5).fetch()
6
return B5
7=A5.conj()/合并并行結(jié)果
8=interval@ms(A4,now())/并行查詢耗時(shí)

代碼3.3

代碼3.3,并行時(shí),A5的每個(gè)線程中都可以使用A2、A3中建立的第三級(jí)索引緩存,最終查詢耗時(shí)為21376毫秒。

?

4 帶值索引

組表的行存和列存形式都支持索引,列存索引查找比行存性能差,返回結(jié)果集較少時(shí)差異不明顯,大量返回時(shí)會(huì)有明顯劣勢(shì),在設(shè)計(jì)存儲(chǔ)方案時(shí)要權(quán)衡。


AB
11234567890qwertyuiopasdfghjklzxcvbnm
2=file("id_600m.ctx")
3=A2.create(#id,data)
4for 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,結(jié)構(gòu)為(#id,data) ,包含6億條記錄,其中:

A1:包含 26 個(gè)英文字母和 10 個(gè)阿拉伯?dāng)?shù)字的字符串。

A2、A3:建立結(jié)構(gòu)為 (id,data) 的組表文件,使用列式存儲(chǔ)方式。

A4:循環(huán) 6000 次,循環(huán)體B4、B5,每次生成 10 萬(wàn)條對(duì)應(yīng)結(jié)構(gòu)的記錄,并追加到組表文件。

執(zhí)行后,生成組表文件:id_600m.ctx

?


A
1=file("id_600m.ctx")
2=A1.create().index(id_idx;id)

代碼4.2

代碼4.2為組表id列建立索引。

執(zhí)行后,生成組表的索引文件:id_600m.ctx__id_idx。

?

列存組表生成時(shí) create() 函數(shù)加上 @r 選項(xiàng),即可變?yōu)樯尚写娼M表,其余代碼無(wú)異,這里不再舉例,當(dāng)返回?cái)?shù)據(jù)量較大時(shí):

?


AB
1=10000.(rand(600000000)+1000000000001).id()/根據(jù)數(shù)據(jù)特點(diǎn),從六億中隨機(jī)取一萬(wàn)個(gè)待查詢的測(cè)試id鍵值并去重
2=file("id_600m.ctx").create()/打開列存組表
3=now()/當(dāng)前時(shí)間
4=A2.icursor(;A1.contain(id),id_idx).fetch()/icursor查詢A1中對(duì)應(yīng)的id鍵值
5=interval@ms(A3,now())/列存查詢耗時(shí)
6=file("id_600m@r.ctx").create()/打開行存組表
7=now()/當(dāng)前時(shí)間
8=A6.icursor(;A1.contain(id),id_idx).fetch()/icursor查詢A1中對(duì)應(yīng)的id鍵值
9=interval@ms(A7,now())/行存查詢耗時(shí)

代碼4.3

代碼4.3中,列存查詢耗時(shí)和行存查詢耗時(shí),也就是A5和A9的值分別為205270和82800毫秒。

?

組表支持一種帶值索引,即把查找字段也寫入索引,這樣可以不再訪問原組表即返回結(jié)果。但存儲(chǔ)空間會(huì)占用較多。

基于代碼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列建立索引,在對(duì)組表建立索引時(shí),當(dāng) index 函數(shù)有數(shù)據(jù)列名參數(shù),如本例 A2 中的 data,就會(huì)在建索引時(shí)把數(shù)據(jù)列 data 復(fù)制進(jìn)索引。當(dāng)有多個(gè)數(shù)據(jù)列時(shí),可以寫為:index(id_idx;id;data1,data2,…)。

因?yàn)樵谒饕凶隽巳哂?,索引文件也自然?huì)較大,本文中測(cè)試的列存組表和索引冗余后的文件大小為:

類型文件名大小
組表id_600m.ctx105G
索引id_600m.ctx__id_data_idx112G

?

當(dāng)數(shù)據(jù)復(fù)制進(jìn)索引后,實(shí)際上讀取時(shí)不再訪問原數(shù)據(jù)文件了。

從 6 億條數(shù)據(jù)總量中取 1 萬(wàn)條批量隨機(jī)鍵值,完整的測(cè)試結(jié)果對(duì)比:

耗時(shí)(毫秒)
單線程多線程(10 線程)
Oracle行存索引冗余索引Oracle行存索引冗余索引
117322207451987339549109759561

?

?

5 批量鍵值

組表索引能夠識(shí)別出contain式條件,支持批量等值查找。


AB
11234567890qwertyuiopasdfghjklzxcvbnm
2=file("id_600m.ctx")
3=A2.create@r(#id,data)
4for 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,結(jié)構(gòu)為(#id,data) ,包含6億條記錄,其中:

A1:包含 26 個(gè)英文字母和 10 個(gè)阿拉伯?dāng)?shù)字的字符串。

A2、A3:建立結(jié)構(gòu)為 (id,data) 的組表文件,@r 選項(xiàng)表示使用行式存儲(chǔ)方式。

A4:循環(huán) 6000 次,循環(huán)體B4、B5,每次生成 10 萬(wàn)條對(duì)應(yīng)結(jié)構(gòu)的記錄,并追加到組表文件。

執(zhí)行后,生成組表文件:id_600m.ctx。

?


A
1=file("id_600m.ctx ? ?")
2=A1.create().index(id_idx;id)

代碼5.2

代碼5.2為組表id列建立索引。

執(zhí)行后,生成組表的索引文件:id_600m.ctx__id_idx


AB
1=file("id_600m.ctx").create()/打開組表
2=now()/當(dāng)前時(shí)間
3=A1.index@3(id_idx)/使用index@3加載索引緩存
4=interval@ms(A2,now())/加載索引緩存的時(shí)間
5=10000.(1000000000000+(rand(600000000)+1)).sort()/隨機(jī)取一些鍵值并排序
6=now()/當(dāng)前時(shí)間
7=A1.icursor(A5.contain(id),id_idx).fetch()/使用icursor函數(shù)進(jìn)行批量鍵值查詢
8=interval@ms(A6,now())/查詢耗時(shí)

代碼5.3

代碼5.3,在組表的 icursor()這個(gè)函數(shù)中,使用索引 id_idx,以條件 A2.contain(id) 來(lái)過(guò)濾組表。集算器會(huì)自動(dòng)識(shí)別出 A2.contain(id) 這個(gè)條件可以使用索引,并會(huì)自動(dòng)將 A2 的內(nèi)容排序后從前向后查找。

進(jìn)階使用

使用排序索引多線程查找時(shí),按鍵值排序分組后扔給多個(gè)線程去查詢,避免兩個(gè)線程中有交叉內(nèi)容。同時(shí),還可以設(shè)計(jì)成多個(gè)組表,把鍵值能平均分配到多個(gè)組表上并行查找。

所謂多線程并行,就是把數(shù)據(jù)分成 N 份,用 N 個(gè)線程查詢。但如果只是隨意地將數(shù)據(jù)分成 N 份,很可能無(wú)法真正地提高性能。因?yàn)閷⒁樵兊逆I值集是未知的,所以理論上也無(wú)法確保希望查找的數(shù)據(jù)能夠均勻分布在每一份組表文件中。比較好的處理方式是先觀察鍵值集的特征,從而盡可能地進(jìn)行數(shù)據(jù)的均勻拆分。

如果鍵值數(shù)據(jù)有比較明顯的業(yè)務(wù)特征,我們可以考慮按照實(shí)際業(yè)務(wù)場(chǎng)景使用日期、部門之類的字段來(lái)處理文件拆分。如:將屬于部門 A 的 1000 條記錄均分在 10 個(gè)文件中,每個(gè)文件就有 100 條記錄。在利用多線程查詢屬于部門 A 的記錄時(shí),每個(gè)線程就會(huì)從各自對(duì)應(yīng)的文件中取數(shù)相應(yīng)的這 100 條記錄了。

下面我們來(lái)看個(gè)實(shí)際的例子,已有數(shù)據(jù)文件multi_source.txt的結(jié)構(gòu)如下:

字段名稱類型說(shuō)明
typestring可枚舉
Idlong每個(gè)枚舉類型的 id 都從 1 開始自增
datastring需要獲取的數(shù)據(jù)

其中 type 和 id 兩個(gè)字段作為聯(lián)合主鍵確定一條記錄,其中部分?jǐn)?shù)據(jù)如下:

性能優(yōu)化技巧 - 查找

?


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()))"))
9for A6,500000

代碼5.4

代碼5.4詳解:

A1:type 的枚舉值組成的序列。在實(shí)際情況中,枚舉列表可能來(lái)自文件或者數(shù)據(jù)庫(kù)數(shù)據(jù)源。。

A2:給枚舉值序列中每個(gè) type 一個(gè) tid。為后續(xù)的數(shù)字化主鍵合并做準(zhǔn)備。

A3~A6:從 multi_source.txt 文件中獲取數(shù)據(jù),并按照 A2 中的對(duì)應(yīng)關(guān)系,把 type 列的枚舉串變成數(shù)字,然后將 type 和 id 進(jìn)行合并后,生成新的主鍵 nid。

A7:使用循環(huán)函數(shù),創(chuàng)建名為“鍵值名 _ 鍵值取 N 的余數(shù) _T.ctx”的組表文件,其結(jié)構(gòu)同為 (#nid,data)。

A8:用循環(huán)函數(shù)將游標(biāo)數(shù)據(jù)分別追加到 N 個(gè)原組表上。比如當(dāng) N=1 時(shí),拼出的 eval 函數(shù)參數(shù)為:channel(A4).select(nid%4==0).attach(A7(1).append(~.cursor()))。意思是對(duì)游標(biāo) A4 創(chuàng)建管道,將管道中記錄按鍵值 nid 取 4 的余數(shù),將余數(shù)值等于 0 的記錄過(guò)濾出來(lái)。attach 是對(duì)當(dāng)前管道的附加運(yùn)算,表示取和當(dāng)前余數(shù)值對(duì)應(yīng)的原組表,將當(dāng)前管道中篩選過(guò)濾出的記錄,以游標(biāo)記錄的方式追加到 A7(1),即第 1 個(gè)組表。

A9:循環(huán)游標(biāo) A6,每次獲取 50 萬(wàn)條記錄,直至 A6 游標(biāo)中的數(shù)據(jù)取完。

執(zhí)行后,產(chǎn)出 4(這時(shí)例子取 N=4)個(gè)獨(dú)立的組表文件:

性能優(yōu)化技巧 - 查找

?


AB
1fork directory@p("nid*T.ctx")=file(A1).create().index(nid_idx;nid;data)

代碼5.5

代碼5.5,創(chuàng)建索引過(guò)程詳解:

A1:列出滿足 nid*T.ctx 的文件名(這里 * 為通配符),這里 @p 選項(xiàng)代表需要返回帶有完整路徑信息的文件名。使用 fork 執(zhí)行多線程時(shí),需要注意環(huán)境中的并行限制數(shù)是否設(shè)置合理。這里用了 4 個(gè)線程,設(shè)計(jì)器中對(duì)應(yīng)的設(shè)置如下:

性能優(yōu)化技巧 - 查找

B1:每個(gè)線程為各個(gè)組表建立對(duì)應(yīng)的索引文件,最終結(jié)果如下:

性能優(yōu)化技巧 - 查找

?


AB
1=file("keys.txt").import@i()
2=A1.group(~%N)
3fork 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,查詢過(guò)程詳解:

A1:從 keys.txt 獲取查詢鍵值序列,因?yàn)橹挥幸涣薪Y(jié)果,使用 @i 選項(xiàng),將結(jié)果返回成序列:

性能優(yōu)化技巧 - 查找

A2:把 A1 的序列按 4 的余數(shù)進(jìn)行等值分組:

性能優(yōu)化技巧 - 查找

A3、B3~B5:用 fork 函數(shù),按等值分組后的鍵值對(duì)各個(gè)組表分別并行查詢。這里的 fork 后面分別寫了兩個(gè)參數(shù),第一個(gè)是循環(huán)函數(shù) N.(~-1),第二個(gè)是 A2。在接下來(lái)的 B3、B4 中分別使用 A3(2) 和 A3(1) 來(lái)獲取 fork 后面這兩個(gè)對(duì)應(yīng)順序的參數(shù),B4:對(duì)組表文件進(jìn)行根據(jù) B3 中的鍵值集進(jìn)行數(shù)據(jù)篩選,B5:返回游標(biāo)。由于 A3 中是多個(gè)線程返回的游標(biāo)序列,所以 A6 中需要使用 conjx 對(duì)多個(gè)游標(biāo)進(jìn)行縱向連接。

A6~A7:將多個(gè)線程返回的游標(biāo)進(jìn)行縱向連接后,導(dǎo)出游標(biāo)記錄至文本文件,前幾行內(nèi)容如下。

性能優(yōu)化技巧 - 查找


向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI