轉載地址:https://www.jianshu.com/p/b8f2af14598a
一、何謂分庫分表?
把原本存儲於一個庫的數據分塊存儲到多個庫(主機)上,把原本存儲於一個表的數據分塊存儲到多個表上。
二、為什么要分庫分表?
數據庫中的數據量不一定是可控的,在未進行分庫分表的情況下,隨着時間和業務的發展,庫中的表會越來越多,表中的數據量也會越來越大,相應地,數據操作,增刪改查的開銷也會越來越大。
另外,由於無法進行分布式式部署,而一台服務器的資源(CPU、磁盤、內存、IO等)是有限的,最終數據庫所能承載的數據量、數據處理能力都將遭遇瓶頸。
三、分庫分表的實施策略
分庫分表有垂直切分和水平切分兩種:
- 依照不同的表(或者Schema)來切分到不同的數據庫(主機)之上,這樣的切分稱之為數據的垂直(縱向)切分。
- 依據表中的數據的邏輯關系,將同一個表中的數據依照某種條件拆分到多台數據庫(主機,當然也可能是同一個數據庫)上面。這樣的切分稱之為數據的水平(橫向)切分。
3.1、數據的垂直切分

將數據庫想象成由非常多個一大塊一大塊的“數據塊”(表)組成,我們垂直的將這些“數據塊”切開,然后將他們分散到多台數據庫(主機)上面,這樣的切分方法就是一個垂直(縱向)的數據切分。
一個架構設計較好的應用系統,它的總體功能肯定是由非常多個功能模塊所組成的,每一個功能模塊所須要的數據對應到數據庫中就是一個或者多個表。
在架構設計中,各功能模塊相互之間的交互點越少,系統的耦合度就越低,系統各個模塊的維護性以及擴展性也就越好。這樣的系統,實現數據的垂直切分也就越簡單。
這樣的系統,當我們依據功能模塊來進行數據的切分,不同功能模塊的數據存放於不同的數據庫主機中,能夠非常簡單就避免掉跨數據庫的Join存在,同一時候系統架構也非常的清晰。
實際情況下,大部分系統是很難做到全部功能模塊所使用的表全然獨立,這個就涉及到跨節點Join的問題,這個后面去講。
3.2、數據的水平切分

數據的垂直切分基本上能夠簡單的理解為依照表依照模塊來切分數據,而水平切分就不再是依照表或者是功能模塊來切分了。一般來說,簡單的水平切分主要是將某個訪問極其頻繁的大表再依照某個字段的某種規則來分散到多個表之中。每一個表中包括一部分數據。
簡單來說,就是將表中的某些行切分到一個數據庫(表),而另外的某些行又切分到其它的數據庫(表)中。當然,為了能夠比較容易的判定各行數據被切分到哪個數據庫(表)中了,切分總是都須要依照某種特定的規則來進行的。
水平分庫分表的切分規則主要包括如下幾種:
- 按號段分
user_id為區分,1~1000的對應DB1,1001~2000的對應DB2,以此類推;
優點:可部分遷移
缺點:數據分布不均 - hash取模分:
對user_id進行hash,然后用一個特定的數字,比如應用中需要將一個數據庫切分成4個數據庫的話,我們就用4這個數字對user_id的hash值進行取模運算,也就是user_id%4,這樣的話每次運算就有四種可能:結果為0的時候對應DB1;結果為1的時候對應DB2;結果為2的時候對應DB3;結果為3的時候對應DB4,這樣一來就非常均勻的將數據分配到4個DB中。如上圖所示。
優點:數據分布均勻
缺點:數據遷移的時候麻煩,不能按照機器性能分攤數據 - 在認證庫中保存數據庫配置
建立一個DB,這個DB單獨保存user_id到DB的映射關系,每次訪問數據庫的時候都要先查詢一次這個數據庫,以得到具體的DB信息,然后才能進行我們需要的查詢操作。
優點:靈活性強,一對一關系
缺點:每次查詢之前都要多一次查詢,性能大打折扣 - 其他方式
1)按照地理區域:比如按照華東,華南,華北這樣來區分業務。
2)按照時間切分,就是將6個月前,甚至一年前的數據切出去放到另外的一張表,因為隨着時間流逝,這些表的數據被查詢的概率變小,所以沒必要和“熱數據”放在一起,這個也是“冷熱數據分離”。
以上就是通常的開發中我們選擇的方式,有些復雜的項目中可能會混合使用這些方式。
四、數據切分之后的問題解決
數據庫中的數據在經過垂直和(或)水平切分被存放在不同的數據庫(表)主機之后,應用系統面臨的最大問題就是怎樣來讓這些數據源得到較好的整合,當然也包括切分的一個唯一性保障的問題。
綜合來說,主要有以下兩個問題:
- 保證ID全局唯一性。
- 查詢數據結果集合並問題,這里包括跨節點Join的問題,跨節點合並排序分頁問題以及分布式事務問題。
4.1、保證ID全局唯一性
保證ID全局唯一性,主要包括兩個要求:
1)全局唯一性:不能出現重復的ID號。
2)數據遞增:保證下一ID號一定大於上一個ID號。
一般情況下是將ID生成器作為一個獨立模塊,需要生成ID時去調用ID生成器。當然,這里需要避免每次用的時候都去獲取一次,可以定期去獲取一個批次的ID,比如5000個,用完了再去拿,拿到之后存入內存中,內存維護一個集合用來存儲,或者你不用集合,通過值去記錄每次讀取的最新ID,以及他拉取的上限,當用完了一半時,再去觸發拉取一次,這樣不用等待。
簡單例子,一個volatile的線程可見共享變量,每次獲取都去修改他的值,這樣來保證,如果后續壓測發現多線程對同一個共享變量操作會有瓶頸,那么可以切分多個值去分攤壓力。
也可以利用MySQL的自增特性。
比如我要把一個表拆分為4個表,那么我可以通過設置每個表的自增ID的起始值和每次自增的值。
舉個例子,我把一個表拆分為tb_user_1,tb_user_2,tb_user_3,tb_user_4四個張,它們的起始值分別為1、2、3、4,自增的值都是4。如下
# 新建第一個分表,表名為tb_user_1,自增ID從1開始,每次增加4 CREATE TABLE IF NOT EXISTS tb_user_1( id INT(11) NOT NULL AUTO_INCREMENT, title VARCHAR(50), num INT(11) NOT NULL DEFAULT 0, PRIMARY KEY (id) )ENGINE=MyISAM DEFAULT CHARSET=utf8; ALTER TABLE tb_user_1 AUTO_INCREMENT=1; #SET auto_increment_offset=1; SET auto_increment_increment=4; # 查看設置的結果show variables like '%auto_increment%'; # 新建第二個分表,表名為tb_user_2,自增ID從2開始,每次增加4 CREATE TABLE IF NOT EXISTS tb_user_2( id INT(11) NOT NULL AUTO_INCREMENT, title VARCHAR(50), num INT(11) NOT NULL DEFAULT 0, PRIMARY KEY (id) )ENGINE=MyISAM DEFAULT CHARSET=utf8; ALTER TABLE tb_user_2 AUTO_INCREMENT=2; #這個是全局設置,不需要重復執行SET auto_increment_increment = 4; # 新建第三個分表,表名為tb_user_3,自增ID從3開始,每次增加4 CREATE TABLE IF NOT EXISTS tb_user_3( id INT(11) NOT NULL AUTO_INCREMENT, title VARCHAR(50), num INT(11) NOT NULL DEFAULT 0, PRIMARY KEY (id) )ENGINE=MyISAM DEFAULT CHARSET=utf8; ALTER TABLE tb_user_3 AUTO_INCREMENT=3; # 新建第四個分表,表名為tb_user_4,自增ID從4開始,每次增加4 CREATE TABLE IF NOT EXISTS tb_user_4( id INT(11) NOT NULL AUTO_INCREMENT, title VARCHAR(50), num INT(11) NOT NULL DEFAULT 0, PRIMARY KEY (id) )ENGINE=MyISAM DEFAULT CHARSET=utf8; #ALTER TABLE tb_user_4 AUTO_INCREMENT=4; # 插入數據進行測試 INSERT INTO tb_user_1(title, num) VALUES('我是一個好人',234); INSERT INTO tb_user_2(title, num) VALUES('我是一個好人2',234); INSERT INTO tb_user_3(title, num) VALUES('我是一個好人3',234); INSERT INTO tb_user_4(title, num) VALUES('我是一個好人4',234); INSERT INTO tb_user_1(title, num) VALUES('我是一個好人1',234); INSERT INTO tb_user_1(title, num) VALUES('我是一個好人11',234); INSERT INTO tb_user_1(title, num) VALUES('我是一個好人111',234);
但是有個問題就是,auto_increment_offset和auto_increment_increment都是全局設置的。如果這樣設置之后對其他表的自增ID都有影響,目前不知如何處理。
4.2、查詢數據結果集合並問題
結果集合並問題包括跨節點Join的問題,跨節點合並排序分頁問題以及分布式事務問題。
先說跨節點Join的問題
數據切分之后,會導致有些老的Join語句無法繼續使用。由於Join使用的數據源可能被切分到多個MySQLServer中啦。
如果一定要從MySQL數據數據庫端來直接解決的話,目前我這邊只找到可以通過MySQL的一種特殊的存儲引擎Federated來處理。Federated存儲引擎是MySQL解決類似於Oracle的DBLink之類問題的解決方式,但不同之處在於Federated會保存一份遠端表結構的定義信息在本地。
但是這種解決方案有一個風險點,那就是假設遠端的表結構發生了變更,本地的表定義信息是不會跟着發生對應變化的,那么在這種情況下非常可能造成Query執行出錯,無法得到正確的結果。
這類問題,推薦通過應用程序來進行處理,先在驅動表所在的MySQLServer中取出對應的驅動結果集,然后依據驅動結果集再到被驅動表所在的MySQLServer中取出對應的數據。當然這種解決方案對性能會產生一定的影響,但是除了此法,基本上沒有太多其它更好的解決的方法了。
再說跨節點合並排序分頁問題
一旦進行了數據的水平切分之后,有些排序分頁的Query語句的數據源可能也會被切分到多個節點,這樣造成的直接后果就是這些排序分頁Query無法繼續正常執行。事實上這和跨節點Join是一個道理。數據源存在於多個節點上,要通過一個Query來解決,就和跨節點Join是一樣的操作。
解決的思路大體上和跨節點Join的解決相似,可是有一點和跨節點Join不太一樣。Join非常多時候都有一個驅動與被驅動的關系,所以Join本身涉及到的多個表之間的數據讀取一般都會存在一個順序關系。
排序分頁就不太一樣,排序分頁的數據源基本上能夠說是一個表(或者一個結果集)。本身並不存在一個順序關系,所以在從多個數據源取數據的過程是全然能夠並行的。
作者:小怪聊職場
鏈接:https://www.jianshu.com/p/b8f2af14598a
來源:簡書