Mysql分庫分表實(shí)現(xiàn)方式
1、背景
Mysql作為做流行的開源數(shù)據(jù)庫,在各大互聯(lián)網(wǎng)公司被廣泛使用。通常我們用一個(gè)庫就可以滿足需求,但是隨著業(yè)務(wù)的增長,數(shù)據(jù)量和并發(fā)量迅速膨脹。
Mysql單表數(shù)據(jù)量到百萬以上的時(shí)候,查詢效率就會(huì)受到影響,另外Mysql單庫能承受的并發(fā)也有限。
這個(gè)時(shí)候我們需要做分庫分表,來提高數(shù)據(jù)庫的性能和擴(kuò)展性
2、為什么分庫分表
2.1 分表
單表數(shù)據(jù)量太大會(huì)極大的影響sql執(zhí)行效率,一般來說單表達(dá)到幾百萬的時(shí)候,性能就相對較差了,就需要分表了
2.2 分庫
單個(gè)庫一般最大支持到2000并發(fā),超過就需要分庫了,一個(gè)健康的單庫并發(fā)值最好控制在1000左右
# | 分庫分表前 | 分庫分表后 |
---|---|---|
并發(fā)支撐情況 | MySQL 單機(jī)部署,扛不住高并發(fā) | MySQL 從單機(jī)到多機(jī),能承受的并發(fā)增加了多倍 |
磁盤使用情況 | MySQL 單機(jī)磁盤容量幾乎撐滿 | 拆分為多個(gè)庫,數(shù)據(jù)庫服務(wù)器磁盤使用率大大降低 |
SQL 執(zhí)行性能 | 單表數(shù)據(jù)量太大,SQL 越跑越慢 | 單表數(shù)據(jù)量減少,SQL 執(zhí)行效率明顯提升 |
數(shù)據(jù)量大,就分表;并發(fā)高,就分庫。
2.3 帶來的問題
- join 操作 : 同一個(gè)數(shù)據(jù)庫中的表分布在了不同的數(shù)據(jù)庫中,導(dǎo)致無法使用 join 操作。這樣就導(dǎo)致我們需要手動(dòng)進(jìn)行數(shù)據(jù)的封裝,比如你在一個(gè)數(shù)據(jù)庫中查詢到一個(gè)數(shù)據(jù)之后,再根據(jù)這個(gè)數(shù)據(jù)去另外一個(gè)數(shù)據(jù)庫中找對應(yīng)的數(shù)據(jù)。
- 事務(wù)問題 :同一個(gè)數(shù)據(jù)庫中的表分布在了不同的數(shù)據(jù)庫中,如果單個(gè)操作涉及到多個(gè)數(shù)據(jù)庫,那么數(shù)據(jù)庫自帶的事務(wù)就無法滿足我們的要求了。
- 分布式 id :分庫之后, 數(shù)據(jù)遍布在不同服務(wù)器上的數(shù)據(jù)庫,數(shù)據(jù)庫的自增主鍵已經(jīng)沒辦法滿足生成的主鍵唯一了。我們?nèi)绾螢椴煌臄?shù)據(jù)節(jié)點(diǎn)生成全局唯一主鍵呢?這個(gè)時(shí)候,我們就需要為我們的系統(tǒng)引入分布式 id 了。
3、不同分庫中間件的優(yōu)缺點(diǎn)
3.1 Sharding-jdbc
1、當(dāng)當(dāng)開源的,client層方案
2、支持分庫分表,讀寫分離,分布式ID生成,柔性事物(最大努力送達(dá)型事物,TCC事物)
3、社區(qū)比較活躍
- 優(yōu)點(diǎn): 優(yōu)點(diǎn)在于不用部署,運(yùn)維成本低,不需要代理層的二次轉(zhuǎn)發(fā)請求,性能很高
- 缺點(diǎn): 各個(gè)系統(tǒng)都需要耦合Sharding-jdbc依賴,如果需要升級,各個(gè)系統(tǒng)需要重新部署
適合中小型公司
3.2 Mycat
1、proxy層方案
2、支持的功能完善
3、很流行,社區(qū)非?;钴S
- 優(yōu)點(diǎn): 對各個(gè)項(xiàng)目透明,升級只需要升級中間件
- 缺點(diǎn): 需要部署,自己運(yùn)維一套中間件,運(yùn)維成本高
適合大型公司
3.3 如何選擇
推薦使用sharding-jdbc和mycat:
- 小型公司選用sharding-jdbc,client層方案輕便,而且維護(hù)成本低,不需要額外增派人手,而且中小型公司系統(tǒng)復(fù)雜度會(huì)低一些,項(xiàng)目也沒那么多
- 中大型公司最好還是選用mycat這類proxy層方案,因?yàn)榭赡艽蠊鞠到y(tǒng)和項(xiàng)目非常多,團(tuán)隊(duì)很大,人員充足,那么最好是專門弄個(gè)人來研究和維護(hù)mycat,然后大量項(xiàng)目直接透明使用即可
4、如何拆分
4.1、水平拆分(用的最多)
把一個(gè)表的數(shù)據(jù)拆分到多個(gè)表中,但是每個(gè)表的結(jié)構(gòu)都一樣,把數(shù)據(jù)均勻的放到不同的表里,用多個(gè)表來抗并發(fā)
根據(jù)特定字段來拆分:
比如訂單表根據(jù)訂單ID來拆分,分到32個(gè)庫,每個(gè)庫32張表,那么orderID%32決定落在哪個(gè)庫,orderID/32%32決定落在那個(gè)表。
而且這兒還有兩種分庫分表的方式:
- 一種是按照 range 來分,就是每個(gè)庫一段連續(xù)的數(shù)據(jù),這個(gè)一般是按比如時(shí)間范圍來的,但是這種一般較少用,因?yàn)楹苋菀桩a(chǎn)生熱點(diǎn)問題,大量的流量都打在最新的數(shù)據(jù)上了。
- 是按照某個(gè)字段 hash 一下均勻分散,這個(gè)較為常用。
各自優(yōu)缺點(diǎn):
- range 來分,好處在于說,擴(kuò)容的時(shí)候很簡單,因?yàn)槟阒灰A(yù)備好,給每個(gè)月都準(zhǔn)備一個(gè)庫就可以了,到了一個(gè)新的月份的時(shí)候,自然而然,就會(huì)寫新的庫了;缺點(diǎn),但是大部分的請求,都是訪問最新的數(shù)據(jù)。實(shí)際生產(chǎn)用 range,要看場景。
- hash 分發(fā),好處在于說,可以平均分配每個(gè)庫的數(shù)據(jù)量和請求壓力;壞處在于說擴(kuò)容起來比較麻煩,會(huì)有一個(gè)數(shù)據(jù)遷移的過程,之前的數(shù)據(jù)需要重新計(jì)算 hash 值重新分配到不同的庫或表。
4.2、垂直拆分
把一個(gè)有很多字段的表拆分到多個(gè)表或者多個(gè)庫上去,把訪問頻率高的熱點(diǎn)字段和訪問頻率低的非熱點(diǎn)字段分開放到不同的表。因?yàn)閿?shù)據(jù)庫有緩存,熱點(diǎn)字段越少,緩存里可以存更多的行
5、分庫分表方案確定后,還需要解決以下問題
- 如何實(shí)現(xiàn)數(shù)據(jù)從單庫單表到分庫分表的遷移?
- 表和庫的數(shù)量又遇到瓶頸怎么辦,如何處理?
- 分庫分表后的ID如何處理?
- 分庫分表后的事務(wù)如何處理?
5.1 如何設(shè)計(jì)才可以讓系統(tǒng)從未分庫分表平滑的動(dòng)態(tài)切換到分庫分表上?
停機(jī)遷移
- 一種方式是直接停機(jī)遷移,需要再深夜沒有流量的時(shí)候進(jìn)行
- 把數(shù)據(jù)遷移到分庫分表數(shù)據(jù)庫上,然后重啟系統(tǒng)就可以,這種方式需要停機(jī),并不是平滑遷移
雙寫遷移方案
這種方案不用停機(jī)。
簡單來說,就是在線上系統(tǒng)里面,之前所有寫庫的地方,增刪改操作,除了對老庫增刪改,都加上對新庫的增刪改,這就是所謂的雙寫,同時(shí)寫倆庫,老庫和新庫。
然后系統(tǒng)部署之后,新庫數(shù)據(jù)差太遠(yuǎn),用之前說的導(dǎo)數(shù)工具,跑起來讀老庫數(shù)據(jù)寫新庫,寫的時(shí)候要根據(jù) gmt_modified 這類字段判斷這條數(shù)據(jù)最后修改的時(shí)間,除非是讀出來的數(shù)據(jù)在新庫里沒有,或者是比新庫的數(shù)據(jù)新才會(huì)寫。簡單來說,就是不允許用老數(shù)據(jù)覆蓋新數(shù)據(jù)。
導(dǎo)完一輪之后,有可能數(shù)據(jù)還是存在不一致,那么就程序自動(dòng)做一輪校驗(yàn),比對新老庫每個(gè)表的每條數(shù)據(jù),接著如果有不一樣的,就針對那些不一樣的,從老庫讀數(shù)據(jù)再次寫。反復(fù)循環(huán),直到兩個(gè)庫每個(gè)表的數(shù)據(jù)都完全一致為止。
接著當(dāng)數(shù)據(jù)完全一致了,就 ok 了,基于僅僅使用分庫分表的最新代碼,重新部署一次,不就僅僅基于分庫分表在操作了么,還沒有幾個(gè)小時(shí)的停機(jī)時(shí)間,很穩(wěn)。所以現(xiàn)在基本玩兒數(shù)據(jù)遷移之類的,都是這么干的。
這個(gè)方法的缺點(diǎn)就是代碼里需要同時(shí)支持兩個(gè)庫的過度階段,稍微麻煩一點(diǎn),但是不需要停機(jī),可以平滑遷移。
5.2 動(dòng)態(tài)擴(kuò)容縮容的分庫分表方案?
分庫分表完成后,如果數(shù)據(jù)繼續(xù)增加,原來的方案不能滿足業(yè)務(wù)了,需要繼續(xù)擴(kuò)大更多庫更多表,那么還要再來一次分庫分表嗎?
這樣處理會(huì)很麻煩,因?yàn)楹罄m(xù)可能還不夠,分庫分表數(shù)據(jù)遷移非常費(fèi)勁。那么我們可以采取一次性分足夠多的庫和表,這樣避免了后續(xù)遷移數(shù)據(jù)的問題。
一開始分庫一次性分夠,32庫*32表,一共1024張表,根據(jù)某個(gè) id 先根據(jù) 32 取模路由到庫,再根據(jù) 32 取模路由到庫里的表。
orderId | id % 32 (庫) | id / 32 % 32 (表) |
---|---|---|
259 | 3 | 8 |
1189 | 5 | 5 |
352 | 0 | 11 |
4593 | 17 | 15 |
剛開始的時(shí)候,這個(gè)庫可能就是邏輯庫,建在一個(gè)數(shù)據(jù)庫上的,就是一個(gè) MySQL 服務(wù)器可能建了 n 個(gè)庫,比如 32 個(gè)庫。后面如果要拆分,就是不斷在庫和 MySQL 服務(wù)器之間做遷移就可以了。然后系統(tǒng)配合改一下配置即可。
比如說最多可以擴(kuò)展到 32 個(gè)數(shù)據(jù)庫服務(wù)器,每個(gè)數(shù)據(jù)庫服務(wù)器是一個(gè)庫。如果還是不夠?最多可以擴(kuò)展到 1024 個(gè)數(shù)據(jù)庫服務(wù)器,每個(gè)數(shù)據(jù)庫服務(wù)器上面一個(gè)庫一個(gè)表。因?yàn)樽疃嗍?1024 個(gè)表。
這么搞,是不用自己寫代碼做數(shù)據(jù)遷移的,都交給 DBA 來搞好了,但是 DBA 確實(shí)是需要做一些庫表遷移的工作,但是總比你自己寫代碼,然后抽數(shù)據(jù)導(dǎo)數(shù)據(jù)來的效率高得多吧。
這里對步驟做一個(gè)總結(jié):
- 設(shè)定好幾臺(tái)數(shù)據(jù)庫服務(wù)器,每臺(tái)服務(wù)器上幾個(gè)庫,每個(gè)庫多少個(gè)表,推薦是 32 庫 * 32 表,對于大部分公司來說,可能幾年都夠了。
- 路由的規(guī)則,orderId 模 32 = 庫,orderId / 32 模 32 = 表
- 擴(kuò)容的時(shí)候,申請?jiān)黾痈嗟臄?shù)據(jù)庫服務(wù)器,裝好 MySQL,呈倍數(shù)擴(kuò)容,4 臺(tái)服務(wù)器,擴(kuò)到 8 臺(tái)服務(wù)器,再到 16 臺(tái)服務(wù)器。
- 由 DBA 負(fù)責(zé)將原先數(shù)據(jù)庫服務(wù)器的庫,遷移到新的數(shù)據(jù)庫服務(wù)器上去,庫遷移是有一些便捷的工具的。
- 我們這邊就是修改一下配置,調(diào)整遷移的庫所在數(shù)據(jù)庫服務(wù)器的地址。
- 重新發(fā)布系統(tǒng),上線,原先的路由規(guī)則變都不用變,直接可以基于 n 倍的數(shù)據(jù)庫服務(wù)器的資源,繼續(xù)進(jìn)行線上系統(tǒng)的提供服務(wù)。
6、分庫分表之后,id 主鍵如何處理?
6.1、設(shè)置數(shù)據(jù)庫 sequence 或者表自增字段步長
適合的場景:
- 在用戶防止產(chǎn)生的 ID 重復(fù)時(shí),這種方案實(shí)現(xiàn)起來比較簡單,也能達(dá)到性能目標(biāo)。
- 但是服務(wù)節(jié)點(diǎn)固定,步長也固定,將來如果還要增加服務(wù)節(jié)點(diǎn),就不好搞了。
6.2、UUID
不具有有序性,作為ID對索引不友好,會(huì)導(dǎo)致 B+ 樹索引在寫的時(shí)候有過多的隨機(jī)寫操作(連續(xù)的 ID 可以產(chǎn)生部分順序?qū)懀?,不適合做主鍵。
適合的場景:
- 如果你是要隨機(jī)生成個(gè)什么文件名、編號之類的
- 你可以用 UUID,但是作為主鍵是不能用 UUID 的
6.3、獲取當(dāng)前系統(tǒng)時(shí)間, 并發(fā)高的時(shí)候可能會(huì)重復(fù)
適合的場景:
- 一般如果用這個(gè)方案,是將當(dāng)前時(shí)間跟很多其他的業(yè)務(wù)字段拼接起來,作為一個(gè) id,如果業(yè)務(wù)上你覺得可以接受,那么也是可以的。
- 你可以將別的業(yè)務(wù)字段值跟當(dāng)前時(shí)間拼接起來,組成一個(gè)全局唯一的編號
6.4、snowflake 算法
- snowflake 算法是 twitter 開源的分布式 id 生成算法,采用 Scala 語言實(shí)現(xiàn),
- 是把一個(gè) 64 位的 long 型的 id,1 個(gè) bit 是不用的,用其中的 41 bits 作為毫秒數(shù),用 10 bits 作為工作機(jī)器 id,12 bits 作為序列號。
7、分庫分表后,事務(wù)如何處理?
分庫分表以后,可能會(huì)出現(xiàn)跨庫事務(wù),這種事務(wù)怎么處理呢?
7.1 優(yōu)化方案避免出現(xiàn)跨庫事務(wù)
- 首先我們做分庫分表的時(shí)候,盡量避免出現(xiàn)跨庫的事務(wù)的場景,
- 比如訂單表,分庫的時(shí)候根據(jù)訂單ID開拆分,那么跟訂單相關(guān)的其他表也按訂單ID來拆分,這樣針對一個(gè)訂單的事務(wù)就可以控制在一個(gè)庫中,避免了跨庫事務(wù)。
7.2 跨庫事務(wù)的處理
- 如果無法避免,分庫分表一般是單系統(tǒng)對應(yīng)多個(gè)庫,那么數(shù)據(jù)庫的XA事務(wù)正好可以處理這種場景,
- sharding-jdbc可以支持?jǐn)?shù)據(jù)庫的XA事務(wù),通過@DSTransactional注解就可以實(shí)現(xiàn)跨庫事務(wù)。
Mycat也能支持XA事務(wù)。
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
zabbix監(jiān)控MySQL主從狀態(tài)的方法詳解
這篇文章主要介紹了zabbix--監(jiān)控MySQL主從狀態(tài)的方法,本文圖文并茂給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值 ,需要的朋友可以參考下2019-06-06mysql 5.7.17 安裝配置方法圖文教程(windows10)
這篇文章主要為大家分享了mysql 5.7.17 安裝配置方法圖文教程,具有一定的參考價(jià)值,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-01-01- 從這個(gè)基本語法可以看出,最簡單的SELECT語句是SELECT select_list,實(shí)際上利用這個(gè)最簡單的SELECT語句,你也可以完成許多你期待的功能,首先你能利用它進(jìn)行MySQL所支持的任何運(yùn)算,例如:SELECT 1+1,它將返回2;其次,你也能利用它給變量賦值,而在PHP中,運(yùn)用SELECT語句的這種功能,你就可以自由地運(yùn)用MySQL的函數(shù)為PHP程序進(jìn)行各種運(yùn)算,并賦值給變量。在很多的時(shí)候,你會(huì)發(fā)現(xiàn)MySQL擁有許多比PHP更為功能強(qiáng)大的函數(shù)。2008-04-04
MySQL?字符串轉(zhuǎn)換為數(shù)字的方法小結(jié)
這篇文章主要介紹了MySQL字符串轉(zhuǎn)換為數(shù)字的幾種方法,本文給大家列列舉了三種方法,每種方法通過實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下2022-01-01MySQL:explain結(jié)果中Extra:Impossible?WHERE?noticed?after?rea
這篇文章主要介紹了MySQL:explain結(jié)果中Extra:Impossible?WHERE?noticed?after?reading?const?tables問題,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-12-12