關(guān)于分庫(kù)分表策略的分析和總結(jié)

一.分庫(kù)分表的原因

我個(gè)人覺(jué)得原因其實(shí)很簡(jiǎn)單:
1.隨著單庫(kù)中的數(shù)據(jù)量越來(lái)越大,相應(yīng)的,查詢所需要的時(shí)間也越來(lái)越多,而面對(duì)MySQL這樣的數(shù)據(jù)庫(kù),在進(jìn)行添加一列這樣的操作時(shí)會(huì)有鎖表的操作,期間所有的讀寫(xiě)操作都要等待,這個(gè)時(shí)候,相當(dāng)于數(shù)據(jù)的處理遇到了瓶頸
2.其實(shí)就是有意外發(fā)生的時(shí)候,單庫(kù)發(fā)生意外的時(shí)候,需要修復(fù)的是所有的數(shù)據(jù),而多庫(kù)中的一個(gè)庫(kù)發(fā)生意外的時(shí)候,只需要修復(fù)一個(gè)庫(kù)(當(dāng)然,也可以用物理分區(qū)的方式處理這種問(wèn)題)

二.分庫(kù)分表常用的策略

在我網(wǎng)上搜集的過(guò)程中,以及自己的實(shí)踐,得到的分庫(kù)策略可以簡(jiǎn)單分為以下幾種方式:(如果有不正確的地方,請(qǐng)大家給我指出來(lái),萬(wàn)分感謝)
首先,分為垂直切分和水平切分:
先說(shuō)垂直切分吧,我的認(rèn)為是根據(jù)業(yè)務(wù)的不同,將原先擁有很多字段的表拆分為兩個(gè)或者多個(gè)表,這樣的代價(jià)我個(gè)人覺(jué)得很大,原來(lái)對(duì)這應(yīng)這個(gè)表的關(guān)系,開(kāi)始細(xì)分,需要一定的重構(gòu),而且隨著數(shù)據(jù)量的增多,極有可能還要增加水平切分;
水平切分,數(shù)據(jù)表結(jié)構(gòu),將數(shù)據(jù)分散在多個(gè)表中;



簡(jiǎn)單的示意圖的了解一下。
對(duì)于垂直切分,好像能說(shuō)的并不多,說(shuō)的比較多一點(diǎn)的是水平切分。
水平切分時(shí)候,理想的情況是不進(jìn)行數(shù)據(jù)遷移,無(wú)感知的進(jìn)行,當(dāng)然這就需要一點(diǎn)點(diǎn)小小的分庫(kù)分表的策略。

1.有瑕疵的簡(jiǎn)單分庫(kù)分表(按id的大小分庫(kù)分表)

按照分片鍵(我們這里就用id表示了)的大小來(lái)進(jìn)行分庫(kù)分表,如果你的id是自增的,而且能保證在進(jìn)行分庫(kù)分表后也是自增的,那么能進(jìn)行很好的改造,以id大小水平切分,而且極有可能不用遷移數(shù)據(jù)。

按id大小分庫(kù).PNG

當(dāng)然,這里只列舉了比較小的數(shù)據(jù)量,實(shí)際情況的分庫(kù)的界限還是要依據(jù)具體的情況而定。這樣的分庫(kù)分表,因?yàn)樾碌臄?shù)據(jù)總在一個(gè)庫(kù)里,很可能導(dǎo)致熱點(diǎn)過(guò)于集中(讀寫(xiě)可能集中在一個(gè)庫(kù)中),這是采取這種方式需要考慮的事情。
如果無(wú)法保證你的id是自增長(zhǎng)的,那么你的數(shù)據(jù)就會(huì)凌亂的分散在各個(gè)數(shù)據(jù)庫(kù),這樣熱點(diǎn)確實(shí)就分散了,可是每當(dāng)你增加一個(gè)數(shù)據(jù)庫(kù)的時(shí)候,你就有可能進(jìn)行大量的數(shù)據(jù)遷移,應(yīng)對(duì)這種情況,就是盡量減少數(shù)據(jù)遷移的代價(jià),所以這里運(yùn)用一致性hash的方式進(jìn)行分庫(kù)分表比較好,可以盡可能的減少數(shù)據(jù)遷移,并且也能讓解決熱點(diǎn)過(guò)于集中的問(wèn)題。一致性hash的分庫(kù)策略去百度一下或者谷歌一下應(yīng)該很容易搜到。如果你百度了還是不知道,歡迎你來(lái)跟我討論。
這里按id的大小來(lái)分庫(kù),還可以發(fā)散到按照時(shí)間來(lái)分庫(kù),比如說(shuō)一個(gè)月的數(shù)據(jù)放在一個(gè)庫(kù),這個(gè)使用mycat比較容易實(shí)現(xiàn)按時(shí)間分庫(kù),不過(guò)你需要思考的數(shù)據(jù)的離散性,數(shù)據(jù)集中于一個(gè)兩月,而剩下的幾個(gè)月數(shù)據(jù)稀疏,這樣的又可能需要按照數(shù)據(jù)的生產(chǎn)規(guī)律合并幾個(gè)月到一個(gè)庫(kù)中,使得數(shù)據(jù)分布均勻。

2.比較方便的取模分庫(kù)

一般的取模分庫(kù)分表是就是將id mod n,然后放入數(shù)據(jù)庫(kù)中,這樣能夠使數(shù)據(jù)分散,不會(huì)有熱點(diǎn)的問(wèn)題,那么,剩下的是,在擴(kuò)容的時(shí)候,是否會(huì)有數(shù)據(jù)遷移的問(wèn)題,一般的擴(kuò)容,當(dāng)然是會(huì)有數(shù)據(jù)遷移的。

取模.PNG

例子中,對(duì)3取模,當(dāng)需要擴(kuò)容的時(shí)候(假設(shè)增加兩個(gè)庫(kù)),則對(duì)5取模,這樣的結(jié)果必然是要進(jìn)行數(shù)據(jù)遷移的,但是可以運(yùn)用一些方法,讓它不進(jìn)行數(shù)據(jù)遷移,scale-out擴(kuò)展方案能夠避免在取模擴(kuò)容的時(shí)候進(jìn)行數(shù)據(jù)遷移。這個(gè)方案是我看到的,我個(gè)人覺(jué)得很好的方案了,這是原文。
我也想介紹一下這個(gè)方案(主要想檢測(cè)一下自己理解了沒(méi)):

(1)第一種擴(kuò)容的方式:根據(jù)表的數(shù)據(jù)增加庫(kù)的數(shù)量

首先,我們有一個(gè)數(shù)據(jù)庫(kù)——DB_0,四張表——tb_0,tb_1,tb_2,tb_3
那么我們現(xiàn)在數(shù)據(jù)到數(shù)據(jù)庫(kù)是這樣的:
DB="DB_0"
TB=“tb_"+id%4



當(dāng)數(shù)據(jù)增加,需要進(jìn)行擴(kuò)容的時(shí)候,我增加一個(gè)數(shù)據(jù)庫(kù)DB_1
DB="DB_"+((id%4)/2)
TB=“tb_"+id%4



當(dāng)我們的數(shù)據(jù)繼續(xù)飆升,這個(gè)時(shí)候又需要我們?cè)黾訋?kù),這個(gè)時(shí)候進(jìn)行加庫(kù)操作的時(shí)候,就不是增加一個(gè)庫(kù),而必須是兩個(gè),這樣才能保證不進(jìn)行數(shù)據(jù)遷移。
DB="DB_"+id%4
TB=“tb_"+id%4

這個(gè)時(shí)候到了這個(gè)方案的加庫(kù)上限,不能繼續(xù)加庫(kù)了,否則就要進(jìn)行數(shù)據(jù)遷移,所以這個(gè)方案的弊端還是挺大了,這樣的方式,也應(yīng)該會(huì)造成單表的數(shù)據(jù)量挺大的。

(2)第二種擴(kuò)容的方式:成倍的增加表

首先,我們還是一個(gè)數(shù)據(jù)庫(kù)——DB_0,兩張表——tb_0,tb_1
那么我們現(xiàn)在數(shù)據(jù)到數(shù)據(jù)庫(kù)是這樣的:
DB="DB_0"
TB=“tb_"+id%2



假設(shè)當(dāng)我們數(shù)據(jù)量打到一千萬(wàn)的時(shí)候,我們?cè)黾右粋€(gè)庫(kù),這時(shí)需要我們?cè)黾觾蓮埍韙b_0_1,tb_1_1,并且原來(lái)的DB_0中庫(kù)的表tb_1整表遷移到DB_1中,tb_0和tb_0_1放在DB_0中,tb_1和tb_1_1放到DB1中。
DB="DB_"+id%2
tb:
if(id<1千萬(wàn)) { return "tb_" + id % 2 }
else if(id>=1千萬(wàn)) { return "tb_"+ id % 2 + "_1" }


數(shù)據(jù)的增長(zhǎng)不可能到此為止,當(dāng)增加到兩千萬(wàn)的時(shí)候,我們需要加庫(kù),這個(gè)時(shí)候,按照這種做法,我們需要增加兩個(gè)庫(kù)(DB_2,DB_3)和四張表(tb_0_2,tb_1_2,tb_2_2,tb_3_2),將上次新增的表整表分別放進(jìn)兩個(gè)新的庫(kù)中,然后每個(gè)庫(kù)里再生成一張新表。
DB:
if(id < 1千萬(wàn)) { return "DB_" + id % 2 }
else if(1千萬(wàn) <= id < 2千萬(wàn)) { return "DB_"+ id % 2 +2 }
else if(2千萬(wàn) <= id ) { return "DB_"+ id % 4 }
tb:
if(id < 1千萬(wàn)) { return "tb_" + id % 2 }
else if(1千萬(wàn) <= id < 2千萬(wàn)) { return "tb_"+ id % 2 +"1" }
else if(id >= 2千萬(wàn)) { return "tb
"+ id % 4+"_2" }

值得注意的一點(diǎn),在id超出范圍的時(shí)候,該給怎么樣的提示是值得思考的。

(3)第二種擴(kuò)容的方式:一個(gè)一個(gè)的增加。(我在這里和原文有點(diǎn)出入,大家不看也罷)

上一種方式是成倍的增加,有的時(shí)候往往不需要這樣,現(xiàn)在我們基于上一個(gè)例子的第二階段(1千萬(wàn)到2千萬(wàn)的階段),添加一個(gè)庫(kù)DB_2,新增兩張表tb_0_2,tb_1_2;將tb_0和tb_1放在DB_0中,最為舊文件的查詢,tb_0_1和和tb_1_1分別放入DB_1和DB_2中,再在這兩個(gè)庫(kù)中生成新的表
DB:
if(id < 1千萬(wàn)) { return "DB_0"}
else if(1千萬(wàn) <= id < 2千萬(wàn)) { return "DB_"+ (id % 2 + 1)
else if(id >= 2千萬(wàn)) {return "DB_"+ id%3}
tb:
if(id < 1千萬(wàn)) { return "tb_" + id%2}
else if(1千萬(wàn) <= id < 2千萬(wàn)) { return "tb_"+ (id % 2) +”1“
else if(id >= 2千萬(wàn)) {return "DB
"+ id%2 +"_0"}


第三種擴(kuò)展方式,按照原文的介紹,會(huì)在舊的數(shù)據(jù)庫(kù)中加入新的數(shù)據(jù)庫(kù),而且當(dāng)繼續(xù)擴(kuò)容的時(shí)候,也會(huì)又一定的困難,我這樣的方式,對(duì)于新的擴(kuò)容,比較困難,所以第三種方式總的來(lái)說(shuō)是我認(rèn)為是失敗,我個(gè)人覺(jué)得最優(yōu)的方式是第二種,我這里的id>n是指在數(shù)據(jù)量達(dá)到n這個(gè)數(shù)據(jù)量,而不是指id按大小進(jìn)行比較,那樣的話,和按照id大小進(jìn)行擴(kuò)容又什么區(qū)別,哈哈哈。
總得來(lái)說(shuō),對(duì)于數(shù)據(jù)庫(kù)擴(kuò)容,總得思考方向?yàn)閮牲c(diǎn):一個(gè)是是否進(jìn)行數(shù)據(jù)遷移;一個(gè)是數(shù)據(jù)是否分布均勻,會(huì)不會(huì)造成熱點(diǎn)集中的情況。數(shù)據(jù)遷移也不一定是壞的,這些都依據(jù)場(chǎng)景而定。

二.分庫(kù)分表后的考慮

分庫(kù)分表之后常常會(huì)遇到數(shù)據(jù)分頁(yè)的問(wèn)題,這個(gè)問(wèn)題其實(shí)解決的辦法很多,但是都沒(méi)有一個(gè)完美的方法,總的來(lái)說(shuō),還是需要妥協(xié),例如在不分庫(kù)分表前:select * from t_msg order by time offset 200 limit 100 這樣的語(yǔ)句,在分庫(kù)分表的后,我看到的有這樣幾種處理
方法一:全局視野法(分別從各個(gè)庫(kù)中提取到x+Y的數(shù)據(jù)量進(jìn)行排序提?。?br> 將order by time offset X limit Y,改寫(xiě)成order by time offset 0 limit X+Y。
服務(wù)層對(duì)得到的N*(X+Y)條數(shù)據(jù)進(jìn)行內(nèi)存排序,內(nèi)存排序后再取偏移量X后的Y條記錄。
方法二:業(yè)務(wù)折衷法-禁止跳頁(yè)查詢
用正常的方法取得第一頁(yè)數(shù)據(jù),并得到第一頁(yè)記錄的time_max。
每次翻頁(yè),將order by time offset X limit Y,改寫(xiě)成order by time where time>$time_max limit Y以保證每次只返回一頁(yè)數(shù)據(jù),性能為常量。
方法三:業(yè)務(wù)折衷法-允許模糊數(shù)據(jù)(數(shù)據(jù)分布足夠隨機(jī)的情況下,各分庫(kù)所有非patition key屬性,在各個(gè)分庫(kù)上的數(shù)據(jù)分布,統(tǒng)計(jì)概率情況應(yīng)該是一致的)
將order by time offset X limit Y,改寫(xiě)成order by time offset X/N limit Y/N。
方法四:二次查詢法
將order by time offset X limit Y,改寫(xiě)成order by time offset X/N limit Y;
找到最小值time_min;
between二次查詢,order by time between $$time_min and $time_i_max;
設(shè)置虛擬time_min,找到time_min在各個(gè)分庫(kù)的offset,從而得到time_min在全局的offset;
得到了time_min在全局的offset,自然得到了全局的offset X limit Y。

最后我想說(shuō),不同的業(yè)務(wù)場(chǎng)景對(duì)應(yīng)不同的策略,不能為了追求最新的東西,而忽略真正的業(yè)務(wù)場(chǎng)景,這樣的得不償失。任何一件事都具有兩面性,就看你如何取舍,放之四海而皆準(zhǔn)。不管什么事情都能解決的,所以,遇到問(wèn)題不要慌。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容