你分庫分表的姿勢對麼? 詳談水平分庫分表

2022-09-10 17:12:32 字數 2951 閱讀 5354

一、背景

提起分庫分表,對於大部分伺服器開發來說,其實並不是乙個新鮮的名詞。隨著業務的發展,我們表中的資料量會變的越來越大,欄位也可能隨著業務複雜度的公升高而逐漸增多,我們為了解決單錶的查詢效能問題,一般會進行分表操作。

同時我們業務的使用者活躍度也會越來越高,併發量級不斷加大,那麼可能會達到單個資料庫的處理能力上限。此時我們為了解決資料庫的處理效能瓶頸,一般會進行分庫操作。不管是分庫操作還是分表操作,我們一般都有兩種方式應對,一種是垂直拆分,一種是水平拆分。

此文主要詳細聊一聊,我們最實用最常見的水平分庫分表方式中的一些特殊細節,希望能幫助大家避免走彎路,找到最合適自身業務的分庫分表設計。

【注1】本文中的案例均基於mysql資料庫,下文中的分庫分表統指水平分庫分表。

【注2】後文中提到到m庫n表,均指共m個資料庫,每個資料庫共n個分表,即總表個數其實為m*n。

二、什麼是乙個好的分庫分表方案?

2.1 方案可持續性

前期業務資料量級不大,流量較低的時候,我們無需分庫分表,也不建議分庫分表。但是一旦我們要對業務進行分庫分表設計時,就一定要考慮到分庫分表方案的可持續性。

**那何為可持續性?**其實就是:業務資料量級和業務流量未來進一步公升高達到新的量級的時候,我們的分庫分表方案可以持續使用。

乙個通俗的案例,假定當前我們分庫分表的方案為10庫100表,那麼未來某個時間點,若10個庫仍然無法應對使用者的流量壓力,或者10個庫的磁碟使用即將達到物理上限時,我們的方案能夠進行平滑擴容。

在後文中我們將介紹下目前業界常用的翻倍擴容法和一致性hash擴容法。

2.2 資料偏斜問題

乙個良好的分庫分表方案,它的資料應該是需要比較均勻的分散在各個庫表中的。如果我們進行乙個拍腦袋式的分庫分表設計,很容易會遇到以下類似問題:

a、某個資料庫例項中,部分表的資料很多,而其他表中的資料卻寥寥無幾,業務上的表現經常是延遲忽高忽低,飄忽不定。

b、資料庫集群中,部分集群的磁碟使用增長特別塊,而部分集群的磁碟增長卻很緩慢。每個庫的增長步調不一致,這種情況會給後續的擴容帶來步調不一致,無法統一操作的問題。

這邊我們定義分庫分表最大資料偏斜率為 :(資料量最大樣本 - 資料量最小樣本)/ 資料量最小樣本。一般來說,如果我們的最大資料偏斜率在5%以內是可以接受的。

三、常見的分庫分表方案

3.1 range分庫分表

顧名思義,該方案根據資料範圍劃分資料的存放位置。

舉個最簡單例子,我們可以把訂單表按照年份為單位,每年的資料存放在單獨的庫(或者表)中。如下圖所示:

* 通過年份分表

* @param orderid

* @return

public static string rangeshardbyyear(string orderid) {

int year = integer.parseint(orderid.substring(0, 4));

return "t_order_" + year;

通過資料的範圍進行分庫分表,該方案是最樸實的一種分庫方案,它也可以和其他分庫分表方案靈活結合使用。時下非常流行的分布式資料庫:tidb資料庫,針對tikv中資料的打散,也是基於range的方式進行,將不同範圍內的[startkey,endkey)分配到不同的region上。

下面我們看看該方案的缺點:鄭州**醫院排名

a、最明顯的就是資料熱點問題,例如上面案例中的訂單表,很明顯當前年度所在的庫表屬於熱點資料,需要承載大部分的io和計算資源。

b、新庫和新錶的追加問題。一般我們線上執行的應用程式是沒有資料庫的建庫建表許可權的,故我們需要提前將新的庫表提前建立,防止線上故障。

這點非常容易被遺忘,尤其是穩定跑了幾年沒有迭代任務,或者人員又交替頻繁的模組。

c、業務上的交叉範圍內資料的處理。舉個例子,訂單模組無法避免一些中間狀態的資料補償邏輯,即需要通過定時任務到訂單表中掃瞄那些長時間處於待支付確認等狀態的訂單。

這裡就需要注意了,因為是通過年份進行分庫分表,那麼元旦的那一天,你的定時任務很有可能會漏掉上一年的最後一天的資料掃瞄。

3.2 hash分庫分表

雖然分庫分表的方案眾多,但是hash分庫分表是最大眾最普遍的方案,也是本文花最大篇幅描述的部分。

在正式介紹這種分庫分表方式之前,我們先看幾個常見的錯誤案例。

常見錯誤案例一:非互質關係導致的資料偏斜問題

public static shardcfg shard(string userid) {

int hash = userid.hashcode();

// 對庫數量取餘結果為庫序號

int dbidx = math.abs(hash % db_cnt);

// 對錶數量取餘結果為表序號

int tblidx = math.abs(hash % tbl_cnt);

return new shardcfg(dbidx, tblidx);

上述方案是初次使用者特別容易進入的誤區,用hash值分別對分庫數和分表數取餘,得到庫序號和表序號。其實稍微思索一下,我們就會發現,以10庫100表為例,如果乙個hash值對100取余為0,那麼它對10取餘也必然為0。

這就意味著只有0庫裡面的0表才可能有資料,而其他庫中的0表永遠為空!

類似的我們還能推導到,0庫裡面的共100張表,只有10張表中(個位數為0的表序號)才可能有資料。這就帶來了非常嚴重的資料偏斜問題,因為某些表中永遠不可能有資料,最大資料偏斜率達到了無窮大。

那麼很明顯,該方案是乙個未達到預期效果的錯誤方案。資料的散落情況大致示意圖如下:

事實上,只要庫數量和表數量非互質關係,都會出現某些表中無資料的問題。

證明如下:

Sharding JDBC 實現水平分庫分表

1 需求分析 cid bigint 20 not null,cname varchar 50 null,user id bigint 20 null,cstatus varchar 10 null,primary key cid create table edu db 1 course 2 cid ...

什麼是垂直分庫分表,水平分庫分表

垂直分片 按照業務拆分的方式稱為垂直分片,又稱為縱向拆分,它的核心理念是專庫專用。在拆分之前,乙個資料庫由多個資料表構成,每個表對應著不同的業務。而拆分之後,則是按照業務將表進行歸類,分布到不同的資料庫中,從而將壓力分散至不同的資料庫。下圖展示了根據業務需要,將使用者表和訂單表垂直分片到不同的資料庫...

水平分庫分表後的分頁查詢

分庫後,分頁查詢按照時間time來排序order by。若查詢第x頁的資料,每頁y條。一共n個庫。步驟 將order by time offset x y 1 limit y,改寫成order by time offset 0 limit x y 1 y 服務層將改寫後的sql語句發往各個分庫 即每...