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