數(shù)據(jù)庫數(shù)據(jù)太多跑不動(dòng)怎么辦汛兜?
隨著業(yè)務(wù)的增長(zhǎng)巴粪,MySQL中保存的數(shù)據(jù)會(huì)越來越多。此時(shí)粥谬,數(shù)據(jù)庫很容易成為系統(tǒng)性能的一個(gè)瓶頸肛根,單機(jī)存儲(chǔ)容量、IO漏策、CPU處理能力都有限派哲,當(dāng)單表的數(shù)據(jù)量達(dá)到1000W或100G以后,庫表的增刪改查操作面臨著性能大幅下降的問題掺喻。
分庫分表是一種解決辦法芭届。分庫分表實(shí)際上就是對(duì)數(shù)據(jù)進(jìn)行切分。
我們一般可以將數(shù)據(jù)切分感耙,分為兩種方式:?垂直(縱向)切分和?水平(橫向)切分褂乍。
垂直切分
垂直切分常見有 垂直分庫 和 垂直分表 兩種。
1. 垂直分庫
垂直分庫就是根據(jù)業(yè)務(wù)耦合性即硼,將關(guān)聯(lián)度低的不同表存儲(chǔ)在不同的數(shù)據(jù)庫逃片。
思想與“微服務(wù)治理”類似,將系統(tǒng)拆分為多個(gè)業(yè)務(wù)只酥,每個(gè)業(yè)務(wù)使用自己?jiǎn)为?dú)的數(shù)據(jù)庫褥实。
比如下圖:
將應(yīng)用拆分為客戶、存款和貸款三個(gè)業(yè)務(wù)裂允,每個(gè)業(yè)務(wù)使用自己?jiǎn)为?dú)的數(shù)據(jù)庫损离。
2. 垂直分表
垂直分表是基于數(shù)據(jù)庫中的表字段來進(jìn)行的。業(yè)務(wù)中可能存在一些字段比較多的表绝编,表中某些字段長(zhǎng)度較大僻澎,這些長(zhǎng)字段我們又只是偶爾需要用到,這時(shí)候我們就可以考慮將表進(jìn)行垂直拆分了。
將某些不常用的怎棱,但是長(zhǎng)度又很大的字段拎出來放到另外一張表哩俭。
MySQL底層是通過數(shù)據(jù)頁存儲(chǔ)的,一條記錄占用空間過大會(huì)導(dǎo)致跨頁拳恋,造成額外的性能開銷凡资。另外數(shù)據(jù)庫以行為單位將數(shù)據(jù)加載到內(nèi)存中,這樣表中字段長(zhǎng)度較短且訪問頻率較高谬运,內(nèi)存能加載更多的數(shù)據(jù)隙赁,命中率更高,減少了磁盤IO梆暖,從而提升了數(shù)據(jù)庫性能伞访。
垂直切分例子如下圖:
我們將一張包含4個(gè)字段的表拆分為2張表,在業(yè)務(wù)代碼里面轰驳,通過字段C1來進(jìn)行關(guān)聯(lián)厚掷。
3. 垂直切分優(yōu)缺點(diǎn)
優(yōu)點(diǎn):
不同系統(tǒng)可以使用不同的庫表,解決業(yè)務(wù)系統(tǒng)層面的耦合级解,業(yè)務(wù)清晰冒黑;
高并發(fā)場(chǎng)景下,垂直切分一定程度地提升IO勤哗、數(shù)據(jù)庫連接數(shù)抡爹,緩解單機(jī)硬件資源的瓶頸。
缺點(diǎn):
部分查詢需要在業(yè)務(wù)代碼邏輯里面做聚合芒划,增加開發(fā)復(fù)雜度冬竟;
事務(wù)處理復(fù)雜,可能需要在業(yè)務(wù)代碼層面做處理民逼;
不能根本解決單表數(shù)據(jù)量過大的問題泵殴。
水平切分
當(dāng)業(yè)務(wù)難以更細(xì)粒度地進(jìn)行垂直切分,或者切分后單表數(shù)據(jù)依然過大缴挖,存在單庫讀寫袋狞、存儲(chǔ)性能瓶頸時(shí)候,這時(shí)候就可以考慮水平切分了映屋。
水平切分又可以分為 庫內(nèi)分表 和 分庫分表苟鸯。
水平切分是根據(jù)表內(nèi)數(shù)據(jù)的內(nèi)在邏輯關(guān)系,將同一個(gè)表按不同的條件分散到多個(gè)數(shù)據(jù)庫或多個(gè)表中棚点,每個(gè)表中只包含一部分?jǐn)?shù)據(jù)早处,從而使得單個(gè)表的數(shù)據(jù)量變小,達(dá)到分布式的效果瘫析。
1. 庫內(nèi)分表
庫內(nèi)分表就是在同一個(gè)DB上砌梆,將表按照某種條件拆分為多張表默责。
比如一張訂單表,我們可以依據(jù)訂單的日期咸包,按月建表桃序。一月份的訂單放month_201901這張表,二月份的訂單放month_201902這張表烂瘫。
庫內(nèi)分表只解決單表數(shù)據(jù)量過大問題媒熊,但沒有將表分布到不同機(jī)器上,?所有請(qǐng)求還是在一臺(tái)物理機(jī)上競(jìng)爭(zhēng)cpu坟比、內(nèi)存芦鳍、IO,對(duì)于減輕mysql負(fù)載壓力來說幫助不大葛账。
2. 分庫分表
分庫分表就是將表不僅拆分柠衅,而且拆分到不同機(jī)器上。
比如我們騰訊云上的DCDB就是這種處理方法籍琳》蒲纾可以指定一張表的shardKey,然后對(duì)shardKey取hash巩割,根據(jù)hash值將數(shù)據(jù)放到不同的數(shù)據(jù)庫中裙顽,?可以解決單機(jī)物理資源的瓶頸問題付燥。
分庫分表的示例如下:
上面示例先根據(jù)業(yè)務(wù)耦合性垂直分庫宣谈,然后再針對(duì)單個(gè)庫進(jìn)行分庫分表。
3. 分庫分表優(yōu)缺點(diǎn)
優(yōu)點(diǎn):
不存在單庫數(shù)據(jù)量過大键科、高并發(fā)的性能瓶頸闻丑,提升系統(tǒng)穩(wěn)定性和負(fù)載能力;
應(yīng)用端改造較小勋颖,不需要拆分業(yè)務(wù)模塊嗦嗡。
缺點(diǎn):
跨分片的事務(wù)一致性較難保障,一般需要一層中間件饭玲,介于業(yè)務(wù)和DB之間侥祭。對(duì)應(yīng)騰訊云上的DCDB數(shù)據(jù)庫所包含的Proxy層;
跨庫的join關(guān)聯(lián)查詢性能較差茄厘。
分庫分表帶來的問題
分庫分表能有效地緩解單機(jī)和單庫帶來的性能瓶頸和壓力矮冬,突破網(wǎng)絡(luò)IO、磁盤存儲(chǔ)次哈、CPU處理能力的瓶頸胎署,同時(shí)也帶來了一些問題。
1. 事務(wù)一致性問題
當(dāng)更新內(nèi)容同時(shí)分布在不同庫中窑滞,不可避免會(huì)帶來跨庫事務(wù)問題琼牧』煮荩跨分片事務(wù)也是分布式事務(wù),沒有簡(jiǎn)單的方案巨坊,一般可使用”XA協(xié)議”和”兩階段提交”處理撬槽。
分布式事務(wù)能最大限度保證數(shù)據(jù)庫操作的原子性,但在提交事務(wù)時(shí)需要協(xié)調(diào)多個(gè)節(jié)點(diǎn)趾撵,推后了提交事務(wù)的時(shí)間點(diǎn)恢氯,延長(zhǎng)了事務(wù)的執(zhí)行時(shí)間。導(dǎo)致事務(wù)在訪問共享資源時(shí)發(fā)生沖突或死鎖的概率增高鼓寺。隨著數(shù)據(jù)庫節(jié)點(diǎn)的增多勋拟,這種趨勢(shì)會(huì)越來越嚴(yán)重,從而成為系統(tǒng)在數(shù)據(jù)庫層面上水平擴(kuò)展的枷鎖妈候。
2. 跨節(jié)點(diǎn)關(guān)聯(lián)查詢 join 問題
切分之前敢靡,系統(tǒng)中很多列表和詳情頁所需的數(shù)據(jù)可以通過sql join來完成,而切分之后苦银,數(shù)據(jù)可能分布在不同的節(jié)點(diǎn)上啸胧,此時(shí)join帶來的問題就比較麻煩了,考慮到性能幔虏,盡量避免使用join查詢纺念。
解決這個(gè)問題的一些方法:
2.1)全局表:
全局表,也可看做是”數(shù)據(jù)字典表”想括,就是系統(tǒng)中所有模塊都可能依賴的一些表陷谱,為了避免跨庫join查詢,可以將這類表在每個(gè)數(shù)據(jù)庫中都保存一份瑟蜈。這些數(shù)據(jù)通常很少會(huì)進(jìn)行修改烟逊,所以也不擔(dān)心一致性的問題。比如騰訊云上的DCDB铺根,可以創(chuàng)建廣播表宪躯,其實(shí)就是全局表。每個(gè)節(jié)點(diǎn)都有該表的全量數(shù)據(jù)位迂,該表的所有操作都將廣播到所有物理分片(set)中访雪。
2.2)字段冗余
一種典型的反范式設(shè)計(jì),利用空間換時(shí)間掂林,為了性能而避免join查詢臣缀。例如:訂單表保存userId時(shí)候,也將userName冗余保存一份党饮,這樣查詢訂單詳情時(shí)就不需要再去查詢”買家user表”了肝陪。
但這種方法適用場(chǎng)景也有限,比較適用于依賴字段比較少的情況刑顺。而冗余字段的數(shù)據(jù)一致性也較難保證氯窍,就像上面訂單表的例子饲常,買家修改了userName后,是否需要在歷史訂單中同步更新呢狼讨?這也要結(jié)合實(shí)際業(yè)務(wù)場(chǎng)景進(jìn)行考慮贝淤。
2.3)數(shù)據(jù)組裝
在系統(tǒng)層面,分兩次查詢政供,第一次查詢的結(jié)果集中找出關(guān)聯(lián)數(shù)據(jù)id播聪,然后根據(jù)id發(fā)起第二次請(qǐng)求得到關(guān)聯(lián)數(shù)據(jù),最后將獲得到的數(shù)據(jù)進(jìn)行字段拼裝布隔。
3. 跨節(jié)點(diǎn)分頁离陶、排序、函數(shù)問題
跨節(jié)點(diǎn)多庫進(jìn)行查詢時(shí)衅檀,會(huì)出現(xiàn)limit分頁招刨、order by排序等問題。分頁需要按照指定字段進(jìn)行排序哀军,當(dāng)排序字段就是分片字段時(shí)沉眶,通過分片規(guī)則就比較容易定位到指定的分片。
當(dāng)排序字段非分片字段時(shí)杉适,就變得比較復(fù)雜了谎倔。需要先在不同的分片節(jié)點(diǎn)中將數(shù)據(jù)進(jìn)行排序并返回,然后將不同分片返回的結(jié)果集進(jìn)行匯總和再次排序猿推,最終返回給用戶片习。顯然這個(gè)過程是會(huì)降低查詢的效率,對(duì)IO彤守,CPU也會(huì)增加額外負(fù)擔(dān)毯侦。
如下圖所示:
上圖中只是取第一頁的數(shù)據(jù),對(duì)性能影響還不是很大具垫。但是如果取得頁數(shù)很大,情況則變得復(fù)雜很多试幽,因?yàn)楦鞣制?jié)點(diǎn)中的數(shù)據(jù)可能是隨機(jī)的筝蚕,為了排序的準(zhǔn)確性,需要將所有節(jié)點(diǎn)的前N頁數(shù)據(jù)都排序好做合并铺坞,最后再進(jìn)行整體的排序起宽,這樣的操作是很耗費(fèi)CPU和內(nèi)存資源的,所以頁數(shù)越大济榨,系統(tǒng)的性能也會(huì)越差坯沪。
在使用Max、Min擒滑、Sum腐晾、Count之類的函數(shù)進(jìn)行計(jì)算的時(shí)候叉弦,也需要先在每個(gè)分片上執(zhí)行相應(yīng)的函數(shù),然后將各個(gè)分片的結(jié)果集進(jìn)行匯總藻糖、再次計(jì)算淹冰,最終將結(jié)果返回。
4. 全局主鍵避重問題
在分庫分表環(huán)境中巨柒,由于表中數(shù)據(jù)同時(shí)存在不同數(shù)據(jù)庫中樱拴,主鍵平時(shí)使用的自增長(zhǎng)將無用武之地,某個(gè)分區(qū)數(shù)據(jù)庫自生成的ID無法保證全局唯一洋满。因此需要單獨(dú)設(shè)計(jì)全局主鍵晶乔,以避免跨庫主鍵重復(fù)問題。
總結(jié)
分庫分表可以解決一些問題(比如單機(jī)的IO牺勾,CPU瘪弓、磁盤瓶頸問題),但也增添了一些新問題(比如事務(wù)一致性問題禽最,跨分片join問題)腺怯。當(dāng)然隨著一些新的NewSQL技術(shù)的成熟,分庫分表這一方案也不再是業(yè)務(wù)擴(kuò)張后的最優(yōu)選擇了川无,騰訊自研云原生數(shù)據(jù)庫CynosDB就給出了更優(yōu)的解決方案呛占。