数据库性能优化—分库分表
文章出自:阿里巴巴十億級(jí)并發(fā)系統(tǒng)設(shè)計(jì)(2021版)
鏈接:https://pan.baidu.com/s/1lbqQhDWjdZe1CBU-6U4jhA?提取碼:8888?
目錄
如何對(duì)數(shù)據(jù)庫(kù)做垂直拆分
如何對(duì)數(shù)據(jù)庫(kù)做水平拆分
解決分庫(kù)分表引入的問(wèn)題
課程小結(jié)
前一節(jié)課,我們學(xué)習(xí)了在高并發(fā)下數(shù)據(jù)庫(kù)的一種優(yōu)化方案:讀寫(xiě)分離,它就是依靠主從復(fù)制的技術(shù)使得數(shù)據(jù)庫(kù)實(shí)現(xiàn)了數(shù)據(jù)復(fù)制為多份,增強(qiáng)了抵抗大量并發(fā)讀請(qǐng)求的能力,提升了數(shù)據(jù)庫(kù)的查詢性能的同時(shí),也提升了數(shù)據(jù)的安全性,當(dāng)某一個(gè)數(shù)據(jù)庫(kù)節(jié)點(diǎn),無(wú)論是主庫(kù)還是從庫(kù)發(fā)生故障時(shí),我們還有其他的節(jié)點(diǎn)中存儲(chǔ)著全量的數(shù)據(jù),保證數(shù)據(jù)不會(huì)丟失。此時(shí),你的電商系統(tǒng)的架構(gòu)圖變成了下面這樣:
?
這時(shí),公司 CEO 突然傳來(lái)一個(gè)好消息,運(yùn)營(yíng)推廣持續(xù)帶來(lái)了流量,你所設(shè)計(jì)的電商系統(tǒng)的 訂單量突破了五千萬(wàn),訂單數(shù)據(jù)都是單表存儲(chǔ)的,你的壓力倍增,因?yàn)闊o(wú)論是數(shù)據(jù)庫(kù)的查詢 還是寫(xiě)入性能都在下降,數(shù)據(jù)庫(kù)的磁盤(pán)空間也在報(bào)警。所以,你主動(dòng)分析現(xiàn)階段自己需要考慮的問(wèn)題,并尋求高效的解決方式,以便系統(tǒng)能正常運(yùn)轉(zhuǎn)下去。你考慮的問(wèn)題主要有以下幾 點(diǎn):
1. 系統(tǒng)正在持續(xù)不斷地的發(fā)展,注冊(cè)的用戶越來(lái)越多,產(chǎn)生的訂單越來(lái)越多,數(shù)據(jù)庫(kù)中存儲(chǔ)的數(shù)據(jù)也越來(lái)越多,單個(gè)表的數(shù)據(jù)量超過(guò)了千萬(wàn)甚至到了億級(jí)別。這時(shí)即使你使用了索 引,索引占用的空間也隨著數(shù)據(jù)量的增長(zhǎng)而增大,數(shù)據(jù)庫(kù)就無(wú)法緩存全量的索引信息,那么 就需要從磁盤(pán)上讀取索引數(shù)據(jù),就會(huì)影響到查詢的性能了。那么這時(shí)你要如何提升查詢性能呢?
2. 數(shù)據(jù)量的增加也占據(jù)了磁盤(pán)的空間,數(shù)據(jù)庫(kù)在備份和恢復(fù)的時(shí)間變長(zhǎng),你如何讓數(shù)據(jù)庫(kù)系統(tǒng)支持如此大的數(shù)據(jù)量呢?
3. 不同模塊的數(shù)據(jù),比如用戶數(shù)據(jù)和用戶關(guān)系數(shù)據(jù),全都存儲(chǔ)在一個(gè)主庫(kù)中,一旦主庫(kù)發(fā)生故障,所有的模塊兒都會(huì)受到影響,那么如何做到不同模塊的故障隔離呢?
4. 你已經(jīng)知道了,在 4 核 8G 的云服務(wù)器上對(duì) MySQL5.7 做 Benchmark,大概可以支撐 500TPS 和 10000QPS,你可以看到數(shù)據(jù)庫(kù)對(duì)于寫(xiě)入性能要弱于數(shù)據(jù)查詢的能力,那么隨 著系統(tǒng)寫(xiě)入請(qǐng)求量的增長(zhǎng),數(shù)據(jù)庫(kù)系統(tǒng)如何來(lái)處理更高的并發(fā)寫(xiě)入請(qǐng)求呢?
這些問(wèn)題你可以歸納成,數(shù)據(jù)庫(kù)的寫(xiě)入請(qǐng)求量大造成的性能和可用性方面的問(wèn)題,要解決這 些問(wèn)題,你所采取的措施就是對(duì)數(shù)據(jù)進(jìn)行分片,對(duì)數(shù)據(jù)進(jìn)行分片,可以很好地分?jǐn)倲?shù)據(jù)庫(kù)的讀寫(xiě)壓力,也可以突破單機(jī)的存儲(chǔ)瓶頸,而常見(jiàn)的一種方式是對(duì)數(shù)據(jù)庫(kù)做“分庫(kù)分表”。 分庫(kù)分表是一個(gè)很常見(jiàn)的技術(shù)方案,你應(yīng)該有所了解。那你會(huì)說(shuō)了:“既然這個(gè)技術(shù)很普遍,而我又有所了解,那你為什么還要提及這個(gè)話題呢?”因?yàn)橐晕疫^(guò)往的經(jīng)驗(yàn)來(lái)看,不少人會(huì)在“分庫(kù)分表”這里踩坑,主要體現(xiàn)在:
- 對(duì)如何使用正確的分庫(kù)分表方式一知半解,沒(méi)有明白使用場(chǎng)景和方法。比如,一些同學(xué)會(huì)在查詢時(shí)不使用分區(qū)鍵;
- 分庫(kù)分表引入了一些問(wèn)題后,沒(méi)有找到合適的解決方案。比如,會(huì)在查詢時(shí)使用大量連表查詢等等。
本節(jié)課,我就帶你解決這兩個(gè)問(wèn)題,從常人容易踩坑的地方,跳出來(lái)。
如何對(duì)數(shù)據(jù)庫(kù)做垂直拆分
分庫(kù)分表是一種常見(jiàn)的將數(shù)據(jù)分片的方式,它的基本思想是依照某一種策略將數(shù)據(jù)盡量平均 的分配到多個(gè)數(shù)據(jù)庫(kù)節(jié)點(diǎn)或者多個(gè)表中。 不同于主從復(fù)制時(shí)數(shù)據(jù)是全量地被拷貝到多個(gè)節(jié)點(diǎn),分庫(kù)分表后,每個(gè)節(jié)點(diǎn)只保存部分的數(shù)據(jù),這樣可以有效地減少單個(gè)數(shù)據(jù)庫(kù)節(jié)點(diǎn)和單個(gè)數(shù)據(jù)表中存儲(chǔ)的數(shù)據(jù)量,在解決了數(shù)據(jù)存儲(chǔ)瓶頸的同時(shí)也能有效的提升數(shù)據(jù)查詢的性能。同時(shí),因?yàn)閿?shù)據(jù)被分配到多個(gè)數(shù)據(jù)庫(kù)節(jié)點(diǎn)上, 那么數(shù)據(jù)的寫(xiě)入請(qǐng)求也從請(qǐng)求單一主庫(kù)變成了請(qǐng)求多個(gè)數(shù)據(jù)分片節(jié)點(diǎn),在一定程度上也會(huì)提升并發(fā)寫(xiě)入的性能。
比如,我之前做過(guò)一個(gè)直播項(xiàng)目,在這個(gè)項(xiàng)目中,需要存儲(chǔ)用戶在直播間中發(fā)的消息以及直播間中的系統(tǒng)消息,你知道這些消息量極大,有些比較火的直播間有上萬(wàn)條留言是很常見(jiàn)的事兒,日積月累下來(lái)就積攢了幾億的數(shù)據(jù),查詢的性能和存儲(chǔ)空間都扛不住了。沒(méi)辦法,就只能加班加點(diǎn)重構(gòu),啟動(dòng)多個(gè)數(shù)據(jù)庫(kù)來(lái)分?jǐn)倢?xiě)入壓力和容量的壓力,也需要將原來(lái)單庫(kù)的數(shù)據(jù)遷移到新啟動(dòng)的數(shù)據(jù)庫(kù)節(jié)點(diǎn)上,好在最后成功完成分庫(kù)分表和數(shù)據(jù)遷移校驗(yàn)工作,不過(guò)也著實(shí)花費(fèi)了不少的時(shí)間和精力。
數(shù)據(jù)庫(kù)分庫(kù)分表的方式有兩種:一種是垂直拆分,另一種是水平拆分。這兩種方式,在我看來(lái),掌握拆分方式是關(guān)鍵,理解拆分原理是內(nèi)核。所以你在學(xué)習(xí)時(shí),最好可以結(jié)合自身業(yè)務(wù)來(lái)思考。垂直拆分,顧名思義就是對(duì)數(shù)據(jù)庫(kù)豎著拆分,也就是將數(shù)據(jù)庫(kù)的表拆分到多個(gè)不同的數(shù)據(jù)庫(kù) 中。
垂直拆分的原則一般是按照業(yè)務(wù)類型來(lái)拆分,核心思想是專庫(kù)專用,將業(yè)務(wù)耦合度比較高的表拆分到單獨(dú)的庫(kù)中。舉個(gè)形象的例子就是在整理衣服的時(shí)候,將羽絨服、毛衣、T 恤分別放在不同的格子里。這樣可以解決我在開(kāi)篇提到的第三個(gè)問(wèn)題:把不同的業(yè)務(wù)的數(shù)據(jù)分拆到不同的數(shù)據(jù)庫(kù)節(jié)點(diǎn)上,這樣一旦數(shù)據(jù)庫(kù)發(fā)生故障時(shí)只會(huì)影響到某一個(gè)模塊的功能,不會(huì)影響 到整體功能,從而實(shí)現(xiàn)了數(shù)據(jù)層面的故障隔離。
我還是以微博系統(tǒng)為例來(lái)給你說(shuō)明一下。 在微博系統(tǒng)中有和用戶相關(guān)的表,有和內(nèi)容相關(guān)的表,有和關(guān)系相關(guān)的表,這些表都存儲(chǔ)在主庫(kù)中。在拆分后,我們期望用戶相關(guān)的表分拆到用戶庫(kù)中,內(nèi)容相關(guān)的表分拆到內(nèi)容庫(kù)中,關(guān)系相關(guān)的表分拆到關(guān)系庫(kù)中。
對(duì)數(shù)據(jù)庫(kù)進(jìn)行垂直拆分是一種偏常規(guī)的方式,這種方式其實(shí)你會(huì)比較常用,不過(guò)拆分之后, 雖然可以暫時(shí)緩解存儲(chǔ)容量的瓶頸,但并不是萬(wàn)事大吉,因?yàn)?span style="color:#f33b45;">數(shù)據(jù)庫(kù)垂直拆分后依然不能解決某一個(gè)業(yè)務(wù)模塊的數(shù)據(jù)大量膨脹的問(wèn)題,一旦你的系統(tǒng)遭遇某一個(gè)業(yè)務(wù)庫(kù)的數(shù)據(jù)量暴增, 在這個(gè)情況下,你還需要繼續(xù)尋找可以彌補(bǔ)的方式。
比如微博關(guān)系量早已經(jīng)過(guò)了千億,單一的數(shù)據(jù)庫(kù)或者數(shù)據(jù)表已經(jīng)遠(yuǎn)遠(yuǎn)不能滿足存儲(chǔ)和查詢的需求了,這個(gè)時(shí)候,你需要將數(shù)據(jù)拆分到多個(gè)數(shù)據(jù)庫(kù)和數(shù)據(jù)表中,也就是對(duì)數(shù)據(jù)庫(kù)和數(shù)據(jù)表 做水平拆分了。
?
如何對(duì)數(shù)據(jù)庫(kù)做水平拆分
和垂直拆分的關(guān)注點(diǎn)不同,垂直拆分的關(guān)注點(diǎn)在于業(yè)務(wù)相關(guān)性,而水平拆分指的是將單一數(shù) 據(jù)表按照某一種規(guī)則拆分到多個(gè)數(shù)據(jù)庫(kù)和多個(gè)數(shù)據(jù)表中,關(guān)注點(diǎn)在數(shù)據(jù)的特點(diǎn)。 拆分的規(guī)則有下面這兩種:
1. 按照某一個(gè)字段的哈希值做拆分,這種拆分規(guī)則比較適用于實(shí)體表,比如說(shuō)用戶表,內(nèi) 容表,我們一般按照這些實(shí)體表的 ID 字段來(lái)拆分。比如說(shuō)我們想把用戶表拆分成 16 個(gè) 庫(kù),64 張表,那么可以先對(duì)用戶 ID 做哈希,哈希的目的是將 ID 盡量打散,然后再對(duì) 16 取余,這樣就得到了分庫(kù)后的索引值;對(duì) 64 取余,就得到了分表后的索引值。
2. 另一種比較常用的是按照某一個(gè)字段的區(qū)間來(lái)拆分,比較常用的是時(shí)間字段。你知道在內(nèi)容表里面有“創(chuàng)建時(shí)間”的字段,而我們也是按照時(shí)間來(lái)查看一個(gè)人發(fā)布的內(nèi)容。我們可能會(huì)要看昨天的內(nèi)容,也可能會(huì)看一個(gè)月前發(fā)布的內(nèi)容,這時(shí)就可以按照創(chuàng)建時(shí)間的區(qū)間來(lái)分庫(kù)分表,比如說(shuō)可以把一個(gè)月的數(shù)據(jù)放入一張表中,這樣在查詢時(shí)就可以根據(jù)創(chuàng)建時(shí)間先定位數(shù)據(jù)存儲(chǔ)在哪個(gè)表里面,再按照查詢條件來(lái)查詢。
?
一般來(lái)說(shuō),列表數(shù)據(jù)可以使用這種拆分方式,比如一個(gè)人一段時(shí)間的訂單,一段時(shí)間發(fā)布的內(nèi)容。但是這種方式可能會(huì)存在明顯的熱點(diǎn),這很好理解嘛,你當(dāng)然會(huì)更關(guān)注最近我買(mǎi)了什 么,發(fā)了什么,所以查詢的 QPS 也會(huì)更多一些,對(duì)性能有一定的影響。另外,使用這種拆分規(guī)則后,數(shù)據(jù)表要提前建立好,否則如果時(shí)間到了 2020 年元旦,DBA(Database Administrator,數(shù)據(jù)庫(kù)管理員)卻忘記了建表,那么 2020 年的數(shù)據(jù)就沒(méi)有庫(kù)表可寫(xiě)了, 就會(huì)發(fā)生故障了。
數(shù)據(jù)庫(kù)在分庫(kù)分表之后,數(shù)據(jù)的訪問(wèn)方式也有了極大的改變,原先只需要根據(jù)查詢條件到從庫(kù)中查詢數(shù)據(jù)即可,現(xiàn)在則需要先確認(rèn)數(shù)據(jù)在哪一個(gè)庫(kù)表中,再到那個(gè)庫(kù)表中查詢數(shù)據(jù)。這種復(fù)雜度也可以通過(guò)數(shù)據(jù)庫(kù)中間件來(lái)解決,我們?cè)谏弦还?jié)中已經(jīng)有所講解,這里就不再贅述 了,不過(guò),我想再次強(qiáng)調(diào)的是你需要對(duì)所使用數(shù)據(jù)庫(kù)中間件的原理有足夠的了解和足夠強(qiáng)的運(yùn)維上的把控能力。
不過(guò),你要知道的是,分庫(kù)分表雖然能夠解決數(shù)據(jù)庫(kù)擴(kuò)展性的問(wèn)題,但是它也給我們的使用帶來(lái)了一些問(wèn)題。
解決分庫(kù)分表引入的問(wèn)題
分庫(kù)分表引入的一個(gè)最大的問(wèn)題就是引入了分庫(kù)分表鍵,也叫做分區(qū)鍵,也就是我們對(duì)數(shù)據(jù)庫(kù)做分庫(kù)分表所依據(jù)的字段。從分庫(kù)分表規(guī)則中你可以看到,無(wú)論是哈希拆分還是區(qū)間段的拆分,我們首先都需要選取一 個(gè)數(shù)據(jù)庫(kù)字段,這帶來(lái)一個(gè)問(wèn)題是:我們之后所有的查詢都需要帶上這個(gè)字段,才能找到數(shù)據(jù)所在的庫(kù)和表,否則就只能向所有的數(shù)據(jù)庫(kù)和數(shù)據(jù)表發(fā)送查詢命令。如果像上面說(shuō)的要拆分成 16 個(gè)庫(kù)和 64 張表,那么一次數(shù)據(jù)的查詢會(huì)變成 16*64=1024 次查詢,查詢的性能肯定是極差的。
?
當(dāng)然,方法總比問(wèn)題多,針對(duì)這個(gè)問(wèn)題,我們也會(huì)有一些相應(yīng)的解決思路。比如,在用戶庫(kù)中我們使用 ID 作為分區(qū)鍵,這時(shí)如果需要按照昵稱來(lái)查詢用戶時(shí),你可以按照昵稱作為分區(qū)鍵再做一次拆分,但是這樣會(huì)極大的增加存儲(chǔ)成本,如果以后我們還需要按照注冊(cè)時(shí)間來(lái)查詢時(shí)要怎么辦呢,再做一次拆分嗎?
所以最合適的思路是你要建立一個(gè)昵稱和 ID 的映射表,在查詢的時(shí)候要先通過(guò)昵稱查詢到 ID,再通過(guò) ID 查詢完整的數(shù)據(jù),這個(gè)表也可以是分庫(kù)分表的,也需要占用一定的存儲(chǔ)空間,但是因?yàn)楸碇兄挥袃蓚€(gè)字段,所以相比重新做一次拆分還是會(huì)節(jié)省不少的空間的。
分庫(kù)分表引入的另外一個(gè)問(wèn)題是一些數(shù)據(jù)庫(kù)的特性在實(shí)現(xiàn)時(shí)可能變得很困難。比如說(shuō)多表的 join 在單庫(kù)時(shí)是可以通過(guò)一個(gè) SQL 語(yǔ)句完成的,但是拆分到多個(gè)數(shù)據(jù)庫(kù)之后就無(wú)法跨庫(kù)執(zhí)行 SQL 了,不過(guò)好在我們對(duì)于 join 的需求不高,即使有也一般是把兩個(gè)表的數(shù)據(jù)取出后在業(yè)務(wù)代碼里面做篩選,復(fù)雜是有一些,不過(guò)是可以實(shí)現(xiàn)的。再比如說(shuō)在未分庫(kù)分表之前查詢數(shù)據(jù)總數(shù)時(shí)只需要在 SQL 中執(zhí)行 count() 即可,現(xiàn)在數(shù)據(jù)被分散到多個(gè)庫(kù)表中,我們可能要考慮其他的方案,比方說(shuō)將計(jì)數(shù)的數(shù)據(jù)單獨(dú)存儲(chǔ)在一張表中或者記錄在 Redis 里面。
當(dāng)然,雖然分庫(kù)分表會(huì)對(duì)我們使用數(shù)據(jù)庫(kù)帶來(lái)一些不便,但是相比它所帶來(lái)的擴(kuò)展性和性能 方面的提升,我們還是需要做的,因?yàn)?#xff0c;經(jīng)歷過(guò)分庫(kù)分表后的系統(tǒng),才能夠突破單機(jī)的容量 和請(qǐng)求量的瓶頸,就比如說(shuō),我在開(kāi)篇提到的我們的電商系統(tǒng),它正是經(jīng)歷了分庫(kù)分表,才 會(huì)解決訂單表數(shù)據(jù)量過(guò)大帶來(lái)的性能衰減和容量瓶頸。
課程小結(jié)
總的來(lái)說(shuō),在面對(duì)數(shù)據(jù)庫(kù)容量瓶頸和寫(xiě)并發(fā)量大的問(wèn)題時(shí),你可以采用垂直拆分和水平拆分來(lái)解決,不過(guò)你要注意,這兩種方式雖然能夠解決問(wèn)題,但是也會(huì)引入諸如查詢數(shù)據(jù)必須帶上分區(qū)鍵,列表總數(shù)需要單獨(dú)冗余存儲(chǔ)等問(wèn)題。 而且,你需要了解的是在實(shí)現(xiàn)分庫(kù)分表過(guò)程中,數(shù)據(jù)從單庫(kù)單表遷移多庫(kù)多表是一件即繁雜又容易出錯(cuò)的事情,而且如果我們初期沒(méi)有規(guī)劃得當(dāng),后面要繼續(xù)增加數(shù)據(jù)庫(kù)數(shù)或者表數(shù)時(shí),我們還要經(jīng)歷這個(gè)遷移的過(guò)程。所以,從我的經(jīng)驗(yàn)出發(fā),對(duì)于分庫(kù)分表的原則主要有以 下幾點(diǎn):
1. 如果在性能上沒(méi)有瓶頸點(diǎn)那么就盡量不做分庫(kù)分表;
2. 如果要做,就盡量一次到位,比如說(shuō) 16 庫(kù) 64 表就基本能夠滿足為了幾年內(nèi)你的業(yè)務(wù)的需求。
3. 很多的 NoSQL 數(shù)據(jù)庫(kù),例如 H Base、MongoDB 都提供 auto sharding 的特性,如果你的團(tuán)隊(duì)內(nèi)部對(duì)于這些組件比較熟悉,有較強(qiáng)的運(yùn)維能力,那么也可以考慮使用這些 NoSQL 數(shù)據(jù)庫(kù)替代傳統(tǒng)的關(guān)系型數(shù)據(jù)庫(kù)。
其實(shí),在我看來(lái),有很多人并沒(méi)有真正從根本上搞懂為什么要拆分,拆分后會(huì)帶來(lái)哪些問(wèn) 題,只是一味地學(xué)習(xí)大廠現(xiàn)有的拆分方法,從而導(dǎo)致問(wèn)題頻出。所以,你在使用一個(gè)方案解 決一個(gè)問(wèn)題的時(shí)候一定要弄清楚原理,搞清楚這個(gè)方案會(huì)帶來(lái)什么問(wèn)題,要如何來(lái)解決,要 知其然也知其所以然,這樣才能在解決問(wèn)題的同時(shí)避免踩坑。
總結(jié)
以上是生活随笔為你收集整理的数据库性能优化—分库分表的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: AOP—JVM SandBox—快速上手
- 下一篇: 数据库性能优化—SQL优化十大原则