1. 程式人生 > 資料庫 >詳解資料庫中跨庫資料表的運算

詳解資料庫中跨庫資料表的運算

1. 簡單合併(FROM)

所謂跨庫資料表,是指邏輯上同一張資料表被分別儲存在不同資料庫中。其原因有可能是因為資料量太大,放在一個數據庫難以處理,也可能在業務上就需要將生產庫和歷史庫分開。而不同的資料庫,可能只是部署在不同的機器上的同種資料庫,也可能是連型別都不同的資料庫系統。

在面對跨庫資料表,特別是資料庫型別都不相同的情況時,資料庫自帶的工具往往就力所不及了,一般都需要尋找能夠很好地支援多資料來源型別的第三方工具,而集算器,可以說是其中的佼佼者了。下面,我們就針對幾種常見的跨庫混合運算情況詳細討論一下:

跨庫運算,簡單粗暴的思路就是把散佈在各個庫裡的邏輯上相同的資料表合併成一個表,然後在這一個表上進行運算。

例如,在兩個資料庫 HSQL 和 MYSQL 中,分別儲存了一張學生成績表,兩者各自儲存了一部分學生資訊,如下圖所示:

利用集算器,我們可以很容易地將這兩個結構相同的表合併為一個表,集算器的 SPL 指令碼如下:

image.png

A1、A2 和 B1、B2 分別讀取了兩個庫裡的學生成績表,而 A3 用一種簡單直觀的方式就把兩個表合併了。

這種方式實際上是把兩個表都讀入了記憶體,分別生成了集算器的序表物件,然後利用序表的運算“|”完成了合併。可能有的同學會問:如果我的資料量比較大,無法全部讀入記憶體怎麼辦?沒關係,專為處理大資料而生的集算器,決不會被這麼簡單的小問題難住。我們可以使用遊標,同樣可以實現表的快速拼接:

image.png

A2、B2 分別用遊標開啟兩個庫裡的學生成績表,A3 則使用 conjx() 函式將這兩個遊標合併,形成了一個新的可以同時訪問兩個表的遊標。

對應於 SQL,這種簡單合併好比只是完成了 from 工作,讓結構相同的跨庫表的資料“縱向”拼接成了一個可以訪問的序表或者遊標,而實際運算中,還會涉及過濾 (where/having)、分組聚合 (group+sum/count/avg/max/min)、連線 (join+on)、去重 (distinct)、排序 (order)、取部分資料 (limit+offset),等等操作,下面我們就將對這些運算一一展開討論。

當然,我們在處理這些運算的需求時,不能只是簡單的實現功能,我們還需要考慮實現的效率和效能,因此原則上,我們會盡量利用資料庫的計算能力,而集算器主要負責混合運算。不過,有時也需要由集算器負責幾乎所有的運算,資料庫僅僅負責儲存資料。

2. WHERE

where 過濾的本質是通過比較計算,去除比較的結果是 false 的記錄,因此 where 只作用於一條記錄,不涉及記錄之間的運算,也不需要考慮資料位於哪個資料庫。比如,在前面的例子中,我們要統計出“一班”所有同學的“數學”成績,單庫中的 SQL 是這樣的:

SELECT 學生 ID,成績 FROM 學生成績表 WHERE 科目 ='數學' AND 班級 =‘一班'

多庫時,也只要將 where 子句直接寫在 SQL 中,讓各個資料庫去並行處理過濾就可以了:

image.png

我們也可以讓集算器負責所有過濾運算,資料庫僅儲存資料。這時可以使用集算器的 select 函式(與 SQL 的 select 關鍵字不同)

image.png

資料量較大時,同樣也可以將序表換成遊標,使用 conjx 函式進行連線:

image.png

3. ORDER BY 和 LIMIT OFFSET

order by 是在結果集產生後才進行的處理。在上面的例子中,如果我們要按數學成績排序,對於單資料庫,只需要加上 order by 子句:

SELECT 班級,學生 ID,成績 FROM 學生成績表 WHERE 科目 ='數學' AND 班級 =‘一班' ORDER BY 成績

而對於多資料庫,可以讓資料庫先分別排序,然後由集算器歸併有序資料。這樣可以最大的發揮資料庫與並行伺服器的效能。

image.png

也可以倒序排序,歸併時在排序欄位前加“-”(merge 函式可以不加“-”,不過按標準寫法是加上的)

image.png

當然也可以完全由集算器來排序:

image.png

由集算器實現倒序排序:

image.png

而對於大資料量,需要使用遊標及 mergex 來完成有序歸併:

image.png

limit 和 offset 的執行又在 order 之後,例子中如果想取數學成績除了第一名之後的前十名(可以少於但不能多於),單庫情況下 SQL 是這樣的:

SELECT 班級,成績 FROM 學生成績表 WHERE 科目 ='數學' AND 班級 =‘一班' ORDER BY 成績 DESC LIMIT 10 OFFSET 1

多資料庫時,可以用集算器的 to 函式實現 limit offset 的功能,to(n+1,n+m) 等同於 limit m offset n

image.png

對於大資料量使用遊標的情況,offset 功能可以使用集算器函式 skip 實現,而 limit 的功能則可以使用函式 fetch 實現

image.png

4. 聚合運算

我們來討論五種常見的聚合運算:sum/count/avg/max/min。

• sum 的基礎是加法,根據加法結合律,各資料庫中內部資料先分別求和,然後拼接成一張表後再求總和,與先拼接成一張表然後一起求和的結果,其實是一樣的。

• count 的本質,是對每項非 null 資料計 1,null 資料計 0,然後進行累加計算。所以其本質仍是加法運算,與 sum 一樣符合加法結合律。唯一不同的是對原始資料不是累加其本身的數值而是計 1(非 null)或計 0(為 null)。

• avg 的本質,是當 count > 0 時 avg = sum/count,當 count = 0 時 avg = null。顯然 avg 不能像 sum 或 count 那樣先分別計算了。不過根據定義,我們可以先算出 sum 和 count,再通過 sum 和 count 計算出 avg。

• max 和 min 的基礎都是比較運算,而因為比較運算具有傳遞性,因此所有資料庫的最值,可以通過比較各個資料庫的最值得到。

依舊是上面的例子,這次我們要求兩個班全體學生的數學總分、人數、平均分、最高及最低分,對於單源資料:

SELECT sum(成績) 總分數,count(成績) 考試人數,avg(成績) 平均分,max(成績) 最高分,min(成績) 最低分 FROM 學生成績表 WHERE 科目 ='數學'

聚合運算的結果集很小,只有一行,因此無論源資料量的大小,都可以使用遊標,程式碼如下:

image.png

事實上,前面提到的 order by +limit offset 本質上也可以看成是一種聚合運算:top。從這個角度進行優化,可以獲得更高的計算效率。畢竟資料量大時,全排序的成本很高,而且取前 N 個數據的操作也並不需要全排序。當然,這個方法對於資料量小的情況也同樣適用。

具體來說,對於 order by F limit m offset n 的情況,只需先用 top(n+m,F,~),再用 to(n+1,) 就行了。

我們仍以之前的含 order by+limit offset 的 SQL 語句為例:

SELECT 班級,成績 FROM 學生成績表 WHERE 科目 ='數學' AND 班級 =‘一班' ORDER BY 成績 DESC LIMIT 10 OFFSET 1

對於多資料庫,指令碼如下,其中倒序排序只需在排序欄位前加“-”:

image.png

5. GROUP BY、DISTINCT 和 HAVING

A、分組聚合運算

對於 group by,因為最終所得結果與樣本個體的輸入順序無關,所以只要樣本的總體不變,最終結果也不會變。也就是說,只要在從分庫中提取資料和最終彙總全部資料時,都預先進行了分類運算即可。

假設我們想分別求一、二班的數學總分、人數、平均分、最高及最低分,單資料庫如下:

SELECT 班級,sum(成績) 總分數,min(成績) 最低分 FROM 學生成績表 WHERE 科目 ='數學' GROUP BY 班級

我們分三種情況討論:

第一,對於小資料,聚合運算的結果集只會更小,這時推薦使用 query+groups:

image.png

第二,對於大資料量,如果結果集也很大,那麼就應該使用 cursor+groupx。

另外,由於大結果集的分組計算較慢,需要在外存產生快取資料。而如果我們在資料庫中對資料先排序,則可以避免這種快取(此時計算壓力會轉到資料庫,因此需要根據實際情況權衡,通常情況下,資料庫伺服器的計算能力會更強一些)。

具體的辦法是對 SQL 的結果集使用 order by 排序,然後在集算器中使用 mergex 函式歸併後,再使用 groupx 的 @o 選項分組:

image.png

當然如果不希望加重資料庫負擔,也可以讓資料庫只做分組而不排序,此時集算器直接用 groupx,注意不能加 @o 選項。另外匯總資料時,也要把 mergex 換成 conjx:

image.png

第三,如果已明確地知道結果集很小,那麼推薦用 cursor+groups

此時 groups 比 groupx 有更好的效能,因為 groups 將運算資料都儲存在記憶體中,比 groupx 節省了寫入外存檔案的時間。

另外用 groups 可以不要求在資料庫中預先排序,因為資料庫 group by 的結果集本身不一定有序,再使用 orde by 排序也會增加成本。而對於小結果集,集算器用 groups@o 也並不一定比直接用 groups 更有效率。

通常,彙總資料要用 conjx

image.png

B、去重後計數 (count distinct)

在各個資料庫內去重,可以使用 distinct 關鍵字。而資料庫之間的資料去重,則可以使用集算器的 merge@u 函式。要注意的是使用前應該確保表內資料對主鍵欄位(或者具有唯一性的一個或多個欄位)有序。

對於 distinct 來說, sum(distinct)、avg(distinct) 的計算方法與 count(distinct) 大同小異,而且業務中不常用到,而 max(distinct)、min(distinct) 與單純使用 max、min 沒有區別。因此,我們只以 count(distinct) 為例加以說明。

比如,想要計算全年級(假設只有一班和二班)語數外三科至少有一科不及格需要補考的總人數,單資料庫的 SQL 是這樣的:

SELECT count(distinct 學生 ID) 人數 FROM 學生成績表 WHERE 成績 <60

對於多源資料,全分組聚合在使用遊標或序表方面沒有差別,為了語法簡便起見以遊標為例:

image.png

再如,想要分別計算每班語數外三科至少有一科不及格需要補考的總人數,單資料庫的 SQL 是這樣的:

SELECT 班級,count(distinct 學生 ID) 人數 FROM 學生成績表 WHERE 成績 <60 GROUP BY 班級

對於多資料庫,同樣需要先彙總去重,再進行分組聚合。彙總前需要資料有序,且彙總後資料仍然有序,所以分組函式 groups 和 groupx 都可以使用 @o 選項。

對於小資料量,可以使用 merge@u、groups@o 和 query:

image.png

對於大資料量小結果集,可以使用 mergex@u、groups@o 和 cursor:

image.png

對於大資料量大結果集,可以使用 mergex@u、groupx@o 和 cursor:

image.png

C、對聚合欄位過濾(having)

having 是對聚合 (分組) 後得出的結果集再做過濾。所以當語句中有 having 出現時,如果聚合 (分組) 操作沒有徹底執行完畢,需要將 having 子句先提取出來。待資料徹底完成聚合 (分組) 操作之後,再執行條件過濾。

對於多源資料,如果聚合計算是在彙總之後才能最終完成,那麼 having 必須使用集算器的函式 select 來實現過濾。

下面主要說明這種聚合計算在彙總之後才完成的情況:比如,想要獲得一班和二班的三個科目的考試中,有哪些平均分是低於 60 分的。對於單資料庫,SQL 可以這樣寫:

SELECT 班級,科目,avg(成績) 平均分 FROM 學生成績表 GROUP BY 班級,科目 HAVING avg(成績)<60

對於多資料庫,相關集算器執行程式碼如下:

image.png

對於大資料量,需要使用遊標 (select 函式同樣適用於遊標)

image.png

6. JOIN ON

跨庫的 JOIN 實現起來非常困難,不過比較幸運的是,我們可以通過儲存設計避免很多跨庫 JOIN。我們分三種情況討論:

1. 同維表分庫,需要重新拼接為一個表

2. 要連線的外來鍵表在每個庫中都有相同的一份

3. 需要連線的外來鍵表在另一個庫中

對於集算器來講,前兩種的處理情況是一樣的:都不需要涉及跨庫 join,join 操作都可以在資料庫內完成。區別只在於第一種是分庫表,資料庫之間沒有重複資料;而第二種則要求把外來鍵表的資料複製到每個庫中。

如果外來鍵表沒有複製到每個庫中,那就會涉及真正的跨庫 join,因為很複雜,這裡只舉一個記憶體外來鍵表的例子,其它更復雜情況會有專門的文章闡述。

A、同維表或主子表同步分庫

所謂同維表,簡單來講就是兩個表的主鍵欄位完全一樣,且其中一個表的主鍵與另一個表的主鍵有邏輯意義上的外來鍵約束(並不要求資料庫中一定有真正的外來鍵,主鍵同理也是邏輯上的主鍵並不一定存在於資料庫中)。

假設有兩個庫,每個庫中有兩個表,分別記為 A 庫中的 A1 表和 A2 表,B 庫中的 B1 表和 B2 表。從邏輯上看 1 表是 A1 表加上 B1 表,2 表是 A2 表加上 B2 表,我們再假設 1 表與 2 表為同維表,現在要做 1 表與 2 表的 join 連線運算。

所謂同步分庫,就是在設計分庫儲存時,保證了 1 表和 2 表按主鍵進行了同步的分割。也就是必須保證分庫之後,A1 和 B2 的 join 等值連線的結果是空集,同樣 A2 和 B1 的 join 等值連線的結果也是空集,這樣也就不必有跨庫的 join 連線運算了。

舉例說明,比如有兩張表:股票資訊與公司資訊,表的結構如下:

公司資訊

股票資訊

兩個表的主鍵都是 (公司程式碼,股票程式碼),且股票資訊的主鍵與公司資訊的主鍵有邏輯意義上的外來鍵約束關係,二者互為同維表。

現在假設我想將兩個表拼接在一起,單資料庫時 SQL 是這樣的:

SELECT * FROM 公司資訊 T1 JOIN 股票資訊 T2 ON T1. 公司程式碼 =T2. 公司程式碼 AND T1. 股票程式碼 = T2. 股票程式碼

現假設公司資訊分為兩部分,分別存於 HSQL 和 MYSQL 資料庫中,股票資訊同樣分為兩部分,分別存於 HSQL 和 MYSQL 資料庫中,且二者是同步分庫。

join 連線公司資訊與股票資訊的集算器程式碼:

image.png

對於大資料:

image.png

主子表的情況與同維表類似,即一個表(主表)的主鍵欄位被另一個表(子表)的主鍵欄位所包含,且子表中對應的主鍵欄位與主表的主鍵有邏輯意義上的外來鍵約束關係。

舉例說明,比如有兩張表:訂單與訂單明細,表的結構如下:

訂單

訂單明細

其中訂單是主表,主鍵為 (訂單 ID);而訂單明細為子表,主鍵為 (訂單 ID,產品 ID),且訂單明細的主鍵欄位訂單 ID,與訂單的主鍵有邏輯意義上的外來鍵約束關係,顯然二者為主子表的關係。

現在假設我想將兩個表拼接在一起,單資料庫的 SQL 是這樣的:

SELECT * FROM 訂單 T1 JOIN 訂單明細 T2 ON T1. 訂單 ID=T2. 訂單 ID

現假設訂單分為兩部分,分別存於 HSQL 和 MYSQL 資料庫中,訂單明細同樣分為兩部分,分別存於 HSQL 和 MYSQL 資料庫中,且二者同步分庫。

join 連線訂單與訂單明細的集算器程式碼:

image.png

對於大資料:

image.png

B、外來鍵表複製進每個庫

所謂外來鍵表,即是指連線欄位為外來鍵欄位的情況。這種外來鍵表 join 也是業務上常見的一種情況。因為要連線的外來鍵表在每個庫中都有同一份,那麼兩個外來鍵表彙總並去重後,其實還是任一資料庫中原來就有的那個外來鍵表。

而 join 的連線操作,本質上可以視為一種乘法,因為 join 連線等價於 cross join 後再用 on 中條件進行過濾。則根據乘法分配率可以推匯出:若是需要做連線操作的外來鍵表(不妨設為連線右側的表)在每個庫中都有同一份,則連線左側的表(每個資料庫中各有其一部分)在彙總後再連線,等同於各資料中的連線左側的表與外來鍵表先做連線操作後,再彙總到一起的結果。如圖所示:

所以我們在儲存設計時,只要在每個資料庫中把外來鍵表都重複一下,就可以避免複雜的跨庫 join 操作。一般情況下,外來鍵表作為維表的資料量相對較小,這樣重複的成本就不會很高,而事實表則會得很大,然後用分庫儲存的方法,來解決運算速度緩慢或儲存空間不足等問題。

例如,有兩個表:客戶銷售表和客戶表,其中客戶銷售表的外來鍵欄位:客戶,與客戶表的主鍵欄位:客戶 ID,有外來鍵約束關係。現在我們想查詢面向河北省各公司的銷售額記錄,對於單資料來源,它的 SQL 是這樣寫的:

SELECT T1. 公司名稱 公司名稱,T2. 訂購日期 訂購日期,T2. 銷售額 銷售額 FROM 客戶表 T1 JOIN 客戶銷售表 T2 ON T1. 客戶 ID=T2. 客戶 WHERE T1. 省份 ='河北'

對於多資料來源的情況,我們假設客戶銷售表分別儲存在兩個不同的資料庫中,而每個資料庫中都有同一份的客戶表做為外來鍵表。則相關的集算器程式碼如下:

image.png

大資料量使用遊標時:

image.png

C、需要連線的外來鍵表在另一個庫中

對於維表(外來鍵表)也被分庫的情況,我們只考慮維表全部可記憶體化的情況,不可記憶體化時,常常就不適合再將資料存在資料庫中了,需要專門針對性的的儲存和計算方案,這將在另外的文章中專門討論。在這裡我們只通過例子來討論維表可記憶體化的情況。

對於這種情況,當涉及的資料量比較大而需要使用遊標時,計算邏輯會變得比較複雜。所以我們在這裡只講一下針對小資料量的使用序表的 join 處理方法。關於對大資料量的使用遊標的 join 處理,會另有一篇文章做專門的介紹。

當要做 join 連線運算的外來鍵表全部或部分儲存在另一個庫中時,最直觀的辦法就是將兩個表都提取出來並各自彙總後,再計算 join 連線。

下面仍以客戶銷售表和客戶表來舉例,假設外來鍵表客戶表也分別儲存在兩個資料庫中,此時就不能在 SQL 中使用 join 關鍵字來實現連線運算了,但我們可以將其提取出來後,用集算器的 join 函式來實現目的,它的集算器程式碼如下所示:

image.png

當事實表資料量較大的時候,也可以使用遊標處理事實表,只需將 join 換成 cs.join 即可:

image.png

7. 簡單 SQL

前面我們主要是從計算原理的角度出發,分析瞭如何使用集算器實現類似 SQL 效果的多資料來源混合計算。除此之外,集算器還提供了一種更簡單、直觀的方法,那就是可以在各個資料庫上通過 SQL 查詢獲取遊標,用所有這些遊標構建成一個多路遊標物件,再用簡單 SQL 對這個多路遊標做二次處理。如果簡單 SQL 中沒有涉及 join 的運算,甚至還可以讓集算器直接將一句簡單 SQL 翻譯成各種資料庫的 SQL,從而實現更進一步的自動化。不過這種辦法屬於比較保守的做法,雖然簡單直接,但不能利用所瞭解的資料情況進行優化(比如不會使用 groups),因此效能就會差一些。

下面仍舊用學生成績的例子,我們想要計算每個班的數學成績的總分、考試人數、平均分、最高分和最低分,使用簡單 SQL 處理這個問題的集算器程式碼如下:

image.png

因為使用了遊標,所以這種寫法也可以用於大資料量。另外再提一句,這個辦法甚至也可以用於非資料庫的資料來源(比如檔案資料來源)!簡單 SQL 的特性可參考相關文件,這裡就不再進一步舉例了。

總結

以上所述是小編給大家介紹的詳解資料庫中跨庫資料表的運算 ,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回覆大家的。在此也非常感謝大家對我們網站的支援!