1. 程式人生 > >實時報表 T+0 的實現方案

實時報表 T+0 的實現方案

一 問題背景

 

在報表的應用系統中,使用者越來越關注資料的實時性,希望最新發生的資料能在報表中體現出來,也就是我們常說的T+0場景, 以此及時輔助決策、驅動運營。

比如交通大資料應用的場景:需要結合實時資料瞭解車輛通行密度,合理進行道路規劃,同時根據歷史資料預測線路擁堵情況、事故多發地提醒等等。

但常規的方案:報表+資料倉庫+ETL工具很難實現此類實時報表,往往只能看到昨天、上週甚至是上個月的情況,也就是T+1、T+7、T+30等,我們統稱為T+n報表。

究其原因,困難大概體現在如下三個方面:

1、如果報表的歷史資料和最新資料都從生產系統讀取,雖然可以實現T+0報表,但是會對生產資料庫造成壓力,當資料量越來越大時,產生效能瓶頸,直接影響業務;並且大量的歷史資料會佔用高昂的資料庫成本(儲存成本和效能成本)。

2、如果採用資料倉庫的方式,那麼ETL從生產庫中取出資料,需要較長的“視窗時間”,一般是業務人員下班之後,到第二天早上上班之前,所以能看到的最新資料也只能是T+1。

3、雖然理論上可以從歷史庫中和生產庫中同時取資料形成實時報表,但是一般的報表工具都不具備跨庫混合計算的能力,其他的跨庫計算方案又比較複雜,難以實施,並且效能較低。

二 解決思路

那麼,是否有成本更低、實施起來更簡單的T+0報表方案呢?下面將要介紹的潤乾集算器,就是這樣一款利器,利用集算器的混合資料來源能力就能實現低成本的T+0實時報表。

實現思路:把不再發生變動的大量歷史資料採用資料檔案儲存,僅從生產庫讀取少量新資料,在保證報表實時性的同時,降低了歷史資料儲存的成本,減少了報表系統對生產資料庫造成的負載。

下圖顯示了常規T+n方案和集算器T+0方案的結構對比,應該說,引入集算器後,減少了很多不必要的成本和多餘的元件,整個體系架構也變得更加清新與合理了:

undefined

上圖新處理方式體系結構中的”匯出(非實時)”是指在非工作時間(例如晚上),定時將生產資料庫的新增資料同步到儲存歷史資料的檔案中;

關於資料外接方案、設計資料儲存組織、定時任務等相關準備和外圍工作,具體做法可參考<<基於檔案系統實現可追加的資料集市>>的相關章節,這裡不再贅述。

三 混合運算場景

下面,我們就通過製作“實時流程工站不良柏拉圖”這個例子,來看一下集算器是如何利用歷史資料結合當期資料進行混合運算,實現T+0方案的。報表最終的展示效果如下圖:

undefined

這張報表清楚地顯示了電子裝置在生產過程中,80%的問題是由20%的原因造成的,對於找出產生大多數問題的關鍵原因很有優勢。

報表中資料的查詢過程是:根據選擇開始日期、結束日期進行過濾查詢;先按照不良程式碼分組,統計彙總每個分類的不良數量,並按照彙總數量降序,然後計算出不良累計比率(演算法為“(不良數量累計彙總/總不良數量彙總)*100”)。報表上部的查詢按鈕是報表工具提供的“引數模板”功能,具體做法參見教程,這裡不再贅述。

3.1編寫資料查詢指令碼

我們假定已經將變化不大的歷史資料搬出了資料庫,採用集檔案(集檔案利用集算器提供的壓縮格式,具有更好IO效能)儲存,命名為MES-pre.btx,同時每天定時執行資料同步指令碼,把前一天的資料追加到當前資料檔案中;查詢涉及的當天少量資料直接從生產資料庫(demo)取出,以此保證資料的實時性。集算器SPL指令碼如下(也支援僅查歷史資料的情況):

 

A

B

1

=connect("demo")

=A1.query("SELECT   code,name FROM watch")

2

[email protected]("SELECT code,nums   FROM meta_resource WHERE  "+if(Efiledate>=date(now()),"DATE_FORMAT(fildate,'%Y-%m-%d')>='"+string(date(now()))+"'","1=0"))

3

=file("D:/PT/MES-pre.btx")[email protected]().select(filedate>=Bfiledate   && filedate<Efiledate)

=[A2,A3].conjx()

4

=B3.groups(code:不良程式碼,code:不良名稱;sum(nums):不良數量,sum(nums):不良累計數量,sum(0):不良累計比率)

5

>A4.switch(不良名稱,B1:code)

=A4.sort(不良數量:-1)

6

>B5.run(不良累計數量+=不良累計數量[-1])

=A4.sum(不良數量)

7

>B5.run(不良累計比率=round(不良累計數量/B6*100,2))

=B5.new(不良程式碼,不良名稱.name:不良名稱,不良數量,不良累計數量,不良累計比率)

8

return B7

 

 

A1:連線預先配置好的生產資料庫(demo)

B1:查詢字典表,不良程式碼、不良名稱

A2:建立資料庫遊標,用簡單的sql讀取資料表的資料。Sql的過濾條件部分會根據邏輯判斷進行動態拼接,當結束日期>=當前系統日期時,代表查詢當天的實時資料,否則做一次結果為空的查詢動作,以適應只查歷史資料的業務場景。@x選項是指讀完資料庫後關閉連線。

A3:建立資料檔案D:/PT/MES-pre.btx的遊標。檔案遊標允許分批從大資料檔案中讀取資料,從而避免記憶體溢位。@b選項是指按照集算器提供的二進位制格式來讀取檔案,同時根據傳入的開始日期(Bfiledate)、結束日期(Efiledate)過濾出符合條件的記錄

B3:將資料庫遊標(新資料)和檔案遊標(歷史資料)合併

A4:利用groups函式,完成對合並後遊標的分組彙總,同時多構造了幾列:不良名稱、不良累計數量、不良累計比率,方便後面的賦值計算。

undefined

A5:通過switch()函式在A4結果的”不良名稱”欄位上建立指向B1表中code欄位的指標引用記錄,實現關聯,如下圖:

undefined

B5:按照不良數量降序排列。如下圖:

undefined

A6:計算不良累計數量;可以看到,集算器用“不良累計數量[-1]”來表示上一行的不良數量,可以輕鬆進行相對位置的計算。

undefined

B6:對不良數量進行總計

A7:計算出不良累計比率,演算法為“(不良數量累計彙總/總不良數量彙總)*100”,同時保留兩位小數,計算結果如下圖:

undefined

B7-A8:取出需要的欄位,將關聯了不良名稱後的結果集返回給報表工具,如下圖:

undefined

3.2作為報表資料來源

在利用集算器完成了資料查詢工作後,為了在報表中使用查詢結果,可以在報表中直接將集算器設定為資料來源,用法和使用資料庫一樣簡單,具體做法如下:

l   在報表中定義引數(Bfiledate、Efiledate),

l   設定集算器資料集,並傳遞報表引數,

l   設計報表統計圖

如下圖所示:

undefined

完成報表設計後,輸入引數進行計算,就可以得到希望的報表了。

四 資料預處理

上一章節中,通過對歷史資料(檔案)和實時資料(資料庫)進行混合計算,就能夠輕鬆實現實時報表(T+0)方案;而為了做到這一點,相應的資料預處理,包括怎麼匯出到檔案、設計怎樣的儲存組織等,也就顯得尤為重要了。

接下來將討論歷史資料匯出到檔案的幾種模式及優缺點分析:冷匯出、折中辦法、熱匯出。

4.1冷匯出

關於用檔案儲存歷史資料能夠帶來的諸多好處,可以參考<<基於檔案系統實現可追加的資料集市>>的相關章節,這裡不再贅述。

所謂冷匯出,就是允許有一段時間視窗,能夠從生產庫取出歷史資料追加匯出到檔案中。例如每天的凌晨2-6點為定時執行任務的時間視窗。

冷匯出的缺點也很明顯,在追加資料匯出到檔案的這段時間裡,這個檔案是不可讀的,也就是說相關的查詢也無法進行了,所以,從本質上說,冷匯出並沒有真正意義上做到T+0實時查詢(生產系統不停機,查詢系統也不停機)。

不過,這裡順便解釋一下:如果使用另一個數據庫儲存歷史資料,就不會有這樣的問題。原因在於關係型資料庫支援事務一致性,資料寫入的同時仍然可以很好地支援查詢。當然這樣做肯定也會犧牲一部分效能,當每天匯出的資料量較多時對資源佔用相當巨大(因為資料庫回滾段會很大)。

所以一致性和高效能在一定程度上是矛盾的。資料庫雖然有一致性,但資料庫本身太慢太貴;而集算器(集檔案)可以獲得高效能,但沒有事務一致性,在維護資料的同時不能參與其他計算。

不過,在對業務場景要求不是很高的情況下,冷匯出也是夠用了,下面我們還是簡單舉例說明一下如何編寫集算器指令碼,獲取昨天的歷史資料追加到當前集檔案中,程式碼如下:

 

A

B

1

=file(“D:/PT/MES-pre.btx”)

=connect("demo")

2

[email protected]("SELECT   * FROM meta_resource WHERE DATE_FORMAT(fildate,'%Y-%m-%d')=?", after(date(now()),-1))

>[email protected](A2)

A1:按路徑開啟需要匯出的集檔案路徑

B1:連線資料庫(demo)

A2:根據sql建立資料庫遊標,獲取昨日資料,引數為昨天日期; @x選項是指讀完資料庫後關閉

B2:執行結果追加寫入到集檔案

4.2折中辦法

針對”冷匯出”方案的不足,比較容易想到的折中辦法就是:歷史資料不再按照追加的模式寫入到一個集檔案中,而是把檔案拆開,讓彼此之間的耦合度更低,互不影響。這樣做的話,就需要考慮以下兩條規則:

1、每天匯出一個獨立的集檔案,可以用年月日命名,這樣匯出過程中,就不會影響對已匯出的歷史資料的查詢。

2、在查詢指令碼中增加時間範圍判斷,規避掉匯出的”時間視窗”;比如定時任務的時間視窗為每天凌晨2-6點;在查詢指令碼中,可以根據查詢動作的當前時間點進行邏輯判斷,如果查詢發生在當天6點以後,說明資料匯出已經完成,那麼資料來源就是集檔案(到昨天為止的歷史資料)+當前資料庫(到今天當前時間點的新資料),若查詢發生在當天6點以前的,那就是集檔案(到前天為止的歷史資料)+當前資料庫(昨天到今天當前時間點的新資料)。

這種辦法的缺點就是在設計資料儲存組織時,檔案會分的比較碎,邏輯判斷部分的程式碼也會顯得比較冗長,而檔案管理也會麻煩一些。但不管怎樣,還是能夠達到要求,實現真正意義上的實時報表(T+0)方案。下面介紹一下實現步驟。

4.2.1設計資料儲存組織

歷史資料按照業務模組進行劃分,每天資料存一份集檔案。目錄結構為:/業務模組/資料明細表/年月日檔名,如下圖所示:

undefined

4.2.2同步昨天資料到檔案

改造“冷匯出”方案中資料匯出指令碼,從資料庫中獲取昨天的歷史資料每天存一份集檔案,用年月日命名,程式碼如下:

 

A

B

1

=file(“D:/PT/”+string(after(date(now()),-1),"yyyyMMdd"))

=connect("demo")

2

[email protected]("SELECT   * FROM meta_resource WHERE DATE_FORMAT(fildate,'%Y-%m-%d')=?", after(date(now()),-1))

>[email protected](A2)

A1:按路徑開啟需要匯出的集檔案路徑,每天一個,用年月日命名

前面已經解釋過的格子的程式碼這裡不再贅述。

4.2.3資料查詢

首先,我們需要寫一個工具指令碼,主要功能是能夠根據傳入的開始日期、結束日期,過濾出需要查詢跨度範圍的多個集檔案路徑,同時判斷路徑下的集檔案物件是否存在。指令碼命名為:判斷讀取檔案的範圍.dfx,編寫程式碼如下:

 

A

1

=if(endDate>=date(now()),if(now()>datetime(concat(date(now()),"  06:00:00")),after(endDate,-1),after(endDate,-2)),endDate)

2

=periods(startDate,A1,1)

3

=A2.(path+string(~,"yyyyMMdd"))

4

=A3.select(file(~).exists())

5

return A4

指令碼接收3個引數,開始日期(startDate),結束日期(endDate),集檔案的儲存路徑(path)

A1:當傳入的結束日期>=當前系統日期時,並且當前時間是在當天6點之後的,返回昨天日期,在當天6點之前的,返回前天日期,否則就返回傳入的實際結束日期

A2:根據開始日期,計算後的結束日期,預設按天間隔獲取日期範圍

A3:迴圈A2,通過集檔案的儲存路徑與該日期段內的年月日進行拼接,利用string()函式進行格式化

A4:判斷路徑下的檔案是否真實存在,由A5返回實際存在的檔案路徑,最終結果如下圖:

undefined

然後,我們需要對前面章節中“混合運算場景”資料查詢的指令碼做一些改造,值得注意的是這裡將採用多路遊標的概念,將多個遊標合併成一個遊標使用,改造後的指令碼如下:

 

A

B

C

1

=connect("demo")

=A1.query("SELECT   code,name FROM watch")

2

[email protected]("SELECT   code,nums FROM meta_resource WHERE  "+if(Efiledate>=date(now()),if(now()>datetime(concat(date(now()),"  06:00:00")),"DATE_FORMAT(fildate,'%Y-%m-%d')>='"+string(date(now()))+"'","DATE_FORMAT(fildate,'%Y-%m-%d')>='"+string(after(date(now()),-1))+"'"),"1=0"))

3

=call("D:/PT/判斷讀取檔案的範圍.dfx",Bfiledate,Efiledate,"D:/PT/資料表A/")

4

=A3.(file(~)[email protected]())

=(A2|A4).mcursor()

 

5

=B4.groups(code:不良程式碼,code:不良名稱;sum(nums):不良數量,sum(nums):不良累計數量,sum(0):不良累計比率)

6

>A5.switch(不良名稱,B1:code)

=A5.sum(不良數量)

=A5.sort(不良數量:-1)

7

>C6.run(不良累計數量+=不良累計數量[-1])

>C6.run(不良累計比率=round((不良累計數量/B6)*100,2))

8

=C6.new(不良程式碼,不良名稱.name:不良名稱,不良數量,不良累計數量,不良累計比率)

9

return A8

   

前面已經解釋過的格子程式碼這裡不再贅述。

A2:建立資料庫遊標,根據邏輯判斷動態拼接sql,當查詢的結束日期>=當前系統日期時,並且當前查詢時間點是在當天6點之後的,只查詢當天的實時資料,當前查詢時間點發生在當天6點之前的,查詢返回昨天和當天的實時資料;否則都不滿足的情況下,做一次結果為空的查詢動作,適應只查詢歷史資料的業務場景。

A3:呼叫”判斷讀取檔案的範圍.dfx”,傳入指令碼引數開始日期、結束日期的值,獲得起止日期內的所有集檔案的集合

A4:迴圈A3,分別開啟每個集檔案物件,根據檔案建立遊標,其中cursor()函式使用@b選項代表從集檔案中讀取。

B4:利用集算器提供的多路遊標概念,把資料結構相同的多個遊標合併成一個遊標使用。使用時,多路遊標採用平行計算來處理各個遊標的資料,可以通過設定cs.mcursor(n) 函式中的n來決定並行數,當n空缺時,將按預設自動設定並行數

A9:最後返回結果集給報表工具使用

4.3熱匯出

所謂熱匯出,是相對於冷匯出而言的。熱匯出要保證查詢系統永不停機,在匯出資料的過程中有查詢請求進來,依然能夠工作。熱匯出一般適用於實時查詢場景要求較高的情況。

4.3.1實現思路

熱匯出需要利用檔案的備份機制結合資料庫的一致性來實現熱切換動作。為了便於理解,可參考以下邏輯圖:

undefined

首先,在資料庫中建備份表,主要目的是為了記錄當前正在使用的是哪個備份檔案,以及從DB中取的熱資料的日期範圍,查詢系統啟動時把這個表清空。

其次,匯出歷史資料到集檔案A,同時備份一個檔案B,然後在資料庫備份表中記錄該檔案A,以及設定從DB中取的熱資料的日期(比如某個時刻之後);這個動作在系統初始化執行時,只做一次。

然後,設計資料查詢的流程:

1、在資料庫中建狀態表,當資料查詢時,先從備份表中查出可用的備份是哪個檔案以及熱資料的日期範圍,然後加入一條記錄到狀態表中,表明該備份檔案正有一個查詢,當查詢完成後將在狀態表中把這條記錄刪除,可以用自增列的方式。

再次,設計資料匯出到集檔案的流程:

1、每天凌晨2點執行定時任務,先同步歷史資料追加到檔案B上,當匯出完成後,修改資料庫備份表的記錄為使用檔案B,同時修改從DB中取熱資料的範圍,以後新產生的查詢動作都將使用檔案B

2、檢查並等待狀態表中A的使用記錄都已清空(基於A的所有查詢都結束了),這時才會同步歷史資料追加到檔案A上,否則每等待1分鐘就迴圈檢查一次。

3、當步驟2的資料追加完成後,再修改資料庫備份表為使用檔案A,以後的新產生的查詢又會回到了使用檔案A,從而達到熱切換的動作。

4、直到等待狀態表中B的使用記錄都清空(基於B的查詢也都結束了)

5、整個過程執行完成,可以等待下一輪匯出

 

這裡需要特別說明的是,備份表、狀態表必須用資料庫作為媒介,從而利用資料庫的一致性;不能用檔案記錄備份表、狀態表的內容,因為檔案無法保持一致性,當多工併發時可能就亂了。

4.3.2資料查詢

第一步,在資料庫中定義”備份表”,包含三個欄位(檔名稱/邊界時間/標識),同時定義”查詢狀態表”,包含三個欄位(唯一標識/檔名稱/當前系統時間,其中定義唯一標識為自增列),資料結構分別如下圖示:

undefined

第二步,通過集檔案A備份一個集檔案B,然後在“備份表”中記錄可查詢的備份檔案為A,並設定從DB中取的熱資料邊界時間(定義為每日的零點),此步操作如果用集算器指令碼執行,樣例程式碼如下:

 

A

B

1

[email protected](file("D:/PT/MES-A.btx"),"D:/PT/MES-B.btx")

=connect("demo")

2

>B1.execute("INSERT   INTO backup (name,crashtime,flag) VALUES   (?,?,?)","A",date(now()),"WORKING_STATUS")

>B1.close()

A1:根據匯出的集檔案A,複製備份同樣的檔案B

A2:備份檔案B完成後,往資料表中寫入當前可用的集檔案A,當前系統時間(零點),給定標識列為:WORKING_STATUS

第三步,我們需要對前面章節中“混合運算場景”資料查詢的指令碼做一些改造,改造後的指令碼如下(此例中也支援僅查歷史資料的情況):

 

A

B

1

=connect("demo")

=A1.query("SELECT   code,name FROM watch")

2

[email protected]("SELECT   NAME,crashtime FROM BACKUP WHERE flag='WORKING_STATUS'")

=name=A2(1),crashtime=A2(2)

3

=A1.cursor("SELECT   code,nums FROM meta_resource WHERE  "+if(Efiledate>=date(crashtime),"DATE_FORMAT(fildate,'%Y-%m-%d')>='"+string(date(crashtime))+"'","1=0"))

>A1.execute("INSERT   INTO status (name,time) VALUES (?,?)",name,now()),uniques   [email protected]("SELECT @@identity")

4

=file(concat("D:/PT/MES-",name,".btx"))[email protected]().select(filedate>=Bfiledate   && filedate<Efiledate)

=[A3,A4].conjx()

5

=B4.groups(code:不良程式碼,code:不良名稱;sum(nums):不良數量,sum(nums):不良累計數量,sum(0):不良累計比率)

6

>A5.switch(不良名稱,B1:code)

=A5.sort(不良數量:-1)

7

>B6.run(不良累計數量+=不良累計數量[-1])

=A5.sum(不良數量)

8

>B6.run(不良累計比率=round((不良累計數量/B7)*100,2))

=B6.new(不良程式碼,不良名稱.name:不良名稱,不良數量,不良累計數量,不良累計比率)

9

>A1.execute("DELETE   FROM STATUS WHERE uniques=?",uniques)

>A1.close()

10

return B8

 

前面已經解釋過的格子程式碼這裡不再贅述。

A2:根據標識WORKING_STATUS作為條件,查詢出來當前可用的集檔名稱,以及熱資料取值的邊界日期時間

B2:定義變數name, crashtime並賦值,便於後面單元格計算引用。

B3:此單元格做了兩步動作,首先,寫入一條記錄到狀態表中,表明該當前備份檔案正有一個查詢,其中uniques為自增列;接著在插入記錄後,通過執行【SELECT @@IDENTITY】獲取上一條插入語句中生成的自增長欄位的值,賦值給變數uniques,便於A9查詢時引用。資料庫中的效果如下圖:

undefined

A9:當查詢完成後,根據變數uniques的值作為條件,在狀態表中把這條記錄刪除,效果如下圖:

undefined

4.3.3同步資料與熱切換

改造“冷匯出”方案中資料匯出指令碼,每天凌晨2點定時執行,程式碼如下:

 

A

B

C

1

=file("D:/PT/MES-B.btx")

=connect("demo")

=file("D:/PT/MES-A.btx")

2

=B1.cursor("SELECT   * FROM meta_resource WHERE DATE_FORMAT(fildate,'%Y-%m-%d')=?",   after(date(now()),-1))

>[email protected](A2)

>B1.execute("UPDATE   BACKUP SET NAME = ?,crashtime=? WHERE flag ='WORKING_STATUS'","B",date(now()))

3

for connect("demo")[email protected]("SELECT   COUNT(*) FROM STATUS WHERE NAME='A'")>0

>sleep(60*1000)

 

4

=B1.cursor("SELECT   * FROM meta_resource WHERE DATE_FORMAT(fildate,'%Y-%m-%d')=?", after(date(now()),-1))

>[email protected](A4)

>B1.execute("UPDATE   BACKUP SET NAME = ?,crashtime=? WHERE flag =  'WORKING_STATUS'","A",date(now()))

5

for connect("demo")[email protected]("SELECT   COUNT(*) FROM STATUS WHERE NAME='B'")>0

>sleep(60*1000)

 

6

>B1.close()

   

前面已經解釋過的格子程式碼這裡不再贅述。

C2:當歷史資料同步追加到檔案B上,修改資料庫”備份表”的記錄為使用檔案B,同時修改從DB中取熱資料的邊界日期範圍,執行結果如下圖:

undefined

A3-B3:迴圈查詢”狀態表”中A的使用記錄是否已清空,若發現還有基於A的查詢沒有結束,那就等待1分鐘,然後接著迴圈,直到基於A的查詢全部結束;

其中A3的資料庫連線表示式需要特別說明一下:通常情況下,在B1單元格中已經定義了資料庫連線,在A3中,可直接引用,寫成:

for [email protected]("SELECT COUNT(*) FROM STATUS WHERE NAME='A'")>0

不過,有些資料庫在預設情況下做成了一次連線只處理一個事務,這樣會導致A3在迴圈的時候結果不會變化,總是按照第一次查詢出來的結果為主,比如第一次查詢返回是true,當資料庫發生變化了,它還是返回true,為了保險期間,可以寫成如下格式:

for connect("demo")[email protected]("SELECT COUNT(*) FROM STATUS WHERE NAME='A'")>0

這個屬於資料庫配置的範疇,可以通過資料庫的連線引數來控制,這裡不再詳解。

C4:當檔案A的資料追加完成後,再修改資料庫”備份表”的記錄為使用檔案A,以後新產生的查詢就會再使用檔案A,執行結果如下圖:

undefined

A5-B5:迴圈查詢”狀態表”中B的使用記錄是否已清空,若發現還有基於B的查詢沒有結束,那就等待1分鐘,然後接著迴圈,直到基於B的查詢全部結束, 此輪整個匯出過程全部完成,然後等待下一輪匯出

五 總結

實時報表(T+0)的場景下,資料的熱匯出是個有些複雜的話題,不過,利用集算器(集檔案)的備份機制結合資料庫的一致性就可以輕鬆應對這類難題了,其中主要用到了以下兩個優勢:

1、跨庫混合計算

集算器作為獨立的計算引擎,可以並行指揮各個資料庫分別計算,收集結果後再進行一輪彙總運算,然後向前端提交或者落地,從而可以很簡單的實現T+0全量查詢報表。

同時,在集算器跨庫混合計算模型下,也不要求資料庫是否同構,歷史資料可以選擇儲存在成本更低的開源資料庫中,例如Oracle和MySQL的混搭叢集。

2、高性價比、高效能的集檔案

無需構建數倉,將歷史資料外接存放到檔案系統中,不僅便於管理,而且可以獲得更高效的IO效能和計算能力,從而很好的解決了關係型資料庫中由於資料量大而導致的效能瓶頸和儲存成本。



作者:Andy
連結:http://c.raqsoft.com.cn/article/1541494770016
來源:乾學院
著作權歸作者所有。商業轉載請聯絡作者獲得授權,非商業轉載請註明出處。