一個數據庫由很多表的構成,每個表對應着不同的業務,垂直切分是指按照業務將表進行分類,分佈到不同的數據庫上面,這樣也就將數據或者說壓力分擔到不同的庫上面。
一、分表
1.如何劃分表
問題:在兩臺主機上的兩個數據庫中的表,能否關聯查詢?
答案:不可以關聯查詢
分庫的原則:有緊密關聯關係的表應該在一個庫裏,相互沒有關聯關係的表可以分到不同的庫裏。
2.schema.xml配置
[root@host79 conf]# pwd /usr/local/mycat/conf [root@host79 conf]# vim schema.xml
<?xml version="1.0"?> <!DOCTYPE mycat:schema SYSTEM "schema.dtd"> <mycat:schema xmlns:mycat="http://io.mycat/">
<schema name="TESTDB" checkSQLschema="false" sqlMaxLimit="100" dataNode="dn1"> <table name="customer" dataNode="dn2" ></table> </schema> <dataNode name="dn1" dataHost="host1" database="orders" /> <dataNode name="dn2" dataHost="host2" database="orders" />
<dataHost name="host1" maxCon="1000" minCon="10" balance="0" writeType="0" dbType="mysql" dbDriver="native" switchType="1" slaveThreshold="100"> <heartbeat>select user()</heartbeat> <!-- can have multi write hosts --> <writeHost host="hostM1" url="192.168.188.188:3306" user="root" password="123456"> </writeHost>
</dataHost> <dataHost name="host2" maxCon="1000" minCon="10" balance="0" writeType="0" dbType="mysql" dbDriver="native" switchType="1" slaveThreshold="100"> <heartbeat>select user()</heartbeat> <!-- can have multi write hosts --> <writeHost host="hostM2" url="192.168.188.189:3306" user="root" password="123456"> </writeHost>
</dataHost>
</mycat:schema> |
3.新增數據庫
分庫操作不是在原來的老數據庫上進行操作,需要準備兩臺機器分別安裝新的數據庫
[root@host79 ~]# mysql -uroot -p123456 mysql> CREATE DATABASE orders; [root@host80 ~]# mysql -uroot -p123456 mysql> CREATE DATABASE orders; |
4.創建表
#啓動 Mycat [root@host79 bin]# ./mycat console
[root@host79 ~]# mysql -umycat -p123456 -h 192.168.188.188 -P 8066 mysql> use TESTDB;
#客戶表 rows:20萬 CREATE TABLE customer( id INT AUTO_INCREMENT, NAME VARCHAR(200), PRIMARY KEY(id) );
#訂單表 rows:600萬 CREATE TABLE orders( id INT AUTO_INCREMENT, order_type INT, customer_id INT, amount DECIMAL(10,2), PRIMARY KEY(id) ); #訂單詳細表 rows:600萬 CREATE TABLE orders_detail( id INT AUTO_INCREMENT, detail VARCHAR(2000), order_id INT, PRIMARY KEY(id) ); #訂單狀態字典表 rows:20 CREATE TABLE dict_order_type( id INT AUTO_INCREMENT, order_type VARCHAR(200), PRIMARY KEY(id) ); |
5.測試
[root@host79 ~]# mysql -uroot -p123456 mysql> use orders; mysql> show tables;
[root@host80 ~]# mysql -uroot -p123456 mysql> use orders; mysql> show tables; |
二、分表
相對於垂直拆分,水平拆分不是將表做分類,而是按照某個字段的某種規則來分散到多個庫之中,每個表中 包含一部分數據。簡單來說,我們可以將數據的水平切分理解爲是按照數據行的切分,就是將表中的某些行切分 到一個數據庫,而另外的某些行又切分到其他的數據庫中。
1、 選擇要拆分的表
MySQL 單表存儲數據條數是有瓶頸的,單表達到 1000 萬條數據就達到了瓶頸,會影響查詢效率,需要進行水平拆分(分表)進行優化。
例如:例子中的 orders、orders_detail 都已經達到 600 萬行數據,需要進行分表優化。
2、 分表字段
以 orders 表爲例,可以根據不同自字段進行分表
3.schema.xml配置文件
#爲 orders 表設置數據節點爲 dn1、dn2,並指定分片規則爲 mod_rule(自定義的名字) <table name="orders" dataNode="dn1,dn2" rule="mod_rule" ></table> |
4.rule.xml配置文件
#在 rule 配置文件裏新增分片規則 mod_rule,並指定規則適用字段爲 customer_id, #還有選擇分片算法 mod-long(對字段求模運算),customer_id 對兩個節點求模,根據結果分片 #配置算法 mod-long 參數 count 爲 2,兩個節點 <tableRule name="mod_rule"> <rule> <columns>customer_id</columns> <algorithm>mod-long</algorithm> </rule> </tableRule>
<function name="mod-long" class="io.mycat.route.function.PartitionByMod"> <!-- how many data nodes --> <property name="count">2</property> </function> |
5.重啓mycat
[root@host79 bin]# ./mycat console |
6.mycat實現分片
#在 mycat 裏向 orders 表插入數據,INSERT 字段不能省略 >mysql INSERT INTO orders(id,order_type,customer_id,amount) VALUES (1,101,100,100100); INSERT INTO orders(id,order_type,customer_id,amount) VALUES(2,101,100,100300); INSERT INTO orders(id,order_type,customer_id,amount) VALUES(3,101,101,120000); INSERT INTO orders(id,order_type,customer_id,amount) VALUES(4,101,101,103000); INSERT INTO orders(id,order_type,customer_id,amount) VALUES(5,102,101,100400); INSERT INTO orders(id,order_type,customer_id,amount) VALUES(6,102,100,100020); #在mycat、dn1、dn2中查看orders表數據,分表成功 |
7.mycat的分片"join"
Orders 訂單表已經進行分表操作了,和它關聯的 orders_detail 訂單詳情表如何進行 join 查詢。
我們要對 orders_detail 也要進行分片操作。Join 的原理如下圖:
[1].Mycat 借鑑了 NewSQL 領域的新秀 Foundation DB 的設計思路,Foundation DB 創新性的提出了 Table Group 的概念,其將子表的存儲位置依賴於主表,並且物理上緊鄰存放,因此徹底解決了JION 的效率和性能問 題,根據這一思路,提出了基於 E-R 關係的數據分片策略,子表的記錄與所關聯的父表記錄存放在同一個數據分片上。
[2].schema.xml
#修改 schema.xml 配置文件 [root@host79 conf]# pwd /usr/local/mycat/conf [root@host79 conf]# vim schema.xml <table name="orders" dataNode="dn1,dn2" rule="mod_rule" > <childTable name="orders_detail" primaryKey="id" joinKey="order_id" parentKey="id" /> </table> |
#在host80下創建orders_detail表 #訂單詳細表 rows:600萬 CREATE TABLE orders_detail( id INT AUTO_INCREMENT, detail VARCHAR(2000), order_id INT, PRIMARY KEY(id) );
#host79重啓mycat #使用host79的mycat進行登錄 #訪問Mycat想oders_detail表插入數據
INSERT INTO orders_detail(id,detail,order_id) values(1,'detail1',1); INSERT INTO orders_detail(id,detail,order_id) VALUES(2,'detail1',2); INSERT INTO orders_detail(id,detail,order_id) VALUES(3,'detail1',3); INSERT INTO orders_detail(id,detail,order_id) VALUES(4,'detail1',4); INSERT INTO orders_detail(id,detail,order_id) VALUES(5,'detail1',5); INSERT INTO orders_detail(id,detail,order_id) VALUES(6,'detail1',6);
#在mycat、host79、host80中運行兩個join語句 Select o.*,od.detail from orders o inner join orders_detail od on o.id=od.order_id; |
1.全局表
在分片的情況下,當業務表因爲規模而進行分片以後,業務表與這些附屬的字典表之間的關聯,就成了比較 棘手的問題,考慮到字典表具有以下幾個特性:
① 變動不頻繁
② 數據量總體變化不大
③ 數據規模不大,很少有超過數十萬條記錄
鑑於此,Mycat 定義了一種特殊的表,稱之爲“全局表”,全局表具有以下特性:
① 全局表的插入、更新操作會實時在所有節點上執行,保持各個分片的數據一致性
② 全局表的查詢操作,只從一個節點獲取
③ 全局表可以跟任何一個表進行 JOIN 操作
將字典表或者符合字典表特性的一些表定義爲全局表,則從另外一個方面,很好的解決了數據JOIN 的難題。通過全局表+基於 E-R 關係的分片策略,Mycat 可以滿足 80%以上的企業應用開發
2.schema.xml
[root@host79 ~]# pwd /root [root@host79 ~]# vim /usr/local/mycat/conf/schema.xml <schema> <table name="dict_order_type" dataNode="dn1,dn2" type="global" ></table> </schema> |
[1].host80創建dict_order_type
mysql> use orders; mysql> create table dict_order_type; |
[2].host79/mycat下插入數據
mysql> use TESTDB;
INSERT INTO dict_order_type(id,order_type) VALUES(101,'type1'); INSERT INTO dict_order_type(id,order_type) VALUES(102,'type2'); |
[3].測試
#host79 mycat mysql>select * from dict_order_type;
#host79 mysql mysql>select * from dict_order_type;
#host80 mysql mysql>select * from dict_order_type; |
分片規則
1.取模
此規則爲對分片字段求摸運算。也是水平分表最常用規則。
|
2.分片枚舉
通過在配置文件中配置可能的枚舉 id,自己配置分片,本規則適用於特定的場景,比如有些業務需要按照省份或區縣來做保存,而全國省份區縣固定的,這類業務使用本條規則。
#(1)修改schema.xml配置文件 <table name="orders_ware_info" dataNode="dn1,dn2" rule="sharding_by_intfile" ></table> #(2)修改rule.xml配置文件 <tableRule name="sharding_by_intfile"> <rule> <columns>areacode</columns> <algorithm>hash-int</algorithm> </rule> </tableRule> … <function name="hash-int" class="io.mycat.route.function.PartitionByFileMap"> <property name="mapFile">partition-hash-int.txt</property> <property name="type">1</property> <property name="defaultNode">0</property> </function> # columns:分片字段,algorithm:分片函數 # mapFile:標識配置文件名稱,type:0爲int型、非0爲String, #defaultNode:默認節點:小於 0 表示不設置默認節點,大於等於 0 表示設置默認節點, # 設置默認節點如果碰到不識別的枚舉值,就讓它路由到默認節點,如不設置不識別就報錯 #(3)修改partition-hash-int.txt配置文件 110=0 120=1 #(4)重啓 Mycat #(5)訪問Mycat創建表 #訂單歸屬區域信息表 CREATE TABLE orders_ware_info ( `id` INT AUTO_INCREMENT comment '編號', `order_id` INT comment '訂單編號', `address` VARCHAR(200) comment '地址', `areacode` VARCHAR(20) comment '區域編號', PRIMARY KEY(id) ); #(6)插入數據 INSERT INTO orders_ware_info(id, order_id,address,areacode) VALUES (1,1,'北京','110'); INSERT INTO orders_ware_info(id, order_id,address,areacode) VALUES (2,2,'天津','120'); #(7)查詢Mycat、dn1、dn2可以看到數據分片效果 |
3.範圍約定
此分片適用於提前規劃好分片字段某個範圍屬於哪個分片。
#(1)修改schema.xml配置文件 <table name="payment_info" dataNode="dn1,dn2" rule="auto_sharding_long" ></table> #(2)修改rule.xml配置文件 <tableRule name="auto_sharding_long"> <rule> <columns>order_id</columns> <algorithm>rang-long</algorithm> </rule> </tableRule> … <function name="rang-long" class="io.mycat.route.function.AutoPartitionByLong"> <property name="mapFile">autopartition-long.txt</property> <property name="defaultNode">0</property> </function> # columns:分片字段,algorithm:分片函數 # mapFile:標識配置文件名稱 #defaultNode:默認節點:小於 0 表示不設置默認節點,大於等於 0 表示設置默認節點, # 設置默認節點如果碰到不識別的枚舉值,就讓它路由到默認節點,如不設置不識別就 報錯 #(3)修改autopartition-long.txt配置文件 0-102=0 103-200=1 #(4)重啓 Mycat #(5)訪問Mycat創建表 #支付信息表 CREATE TABLE payment_info ( `id` INT AUTO_INCREMENT comment '編號', `order_id` INT comment '訂單編號', `payment_status` INT comment '支付狀態', PRIMARY KEY(id) ); #(6)插入數據 INSERT INTO payment_info (id,order_id,payment_status) VALUES (1,101,0); INSERT INTO payment_info (id,order_id,payment_status) VALUES (2,102,1); INSERT INTO payment_info (id,order_id ,payment_status) VALUES (3,103,0); INSERT INTO payment_info (id,order_id,payment_status) VALUES (4,104,1); #(7)查詢Mycat、dn1、dn2可以看到數據分片效果 |
4.按日期(天)分片
#(1)修改schema.xml配置文件 <table name="login_info" dataNode="dn1,dn2" rule="sharding_by_date" ></table> #(2)修改rule.xml配置文件 <tableRule name="sharding_by_date"> <rule> <columns>login_date</columns> <algorithm>shardingByDate</algorithm> </rule> </tableRule> … <function name="shardingByDate" class="io.mycat.route.function.PartitionByDate"> <property name="dateFormat">yyyy-MM-dd</property> <property name="sBeginDate">2019-01-01</property> <property name="sEndDate">2019-01-04</property> <property name="sPartionDay">2</property> </function> # columns:分片字段,algorithm:分片函數 #dateFormat :日期格式 #sBeginDate :開始日期 #sEndDate:結束日期,則代表數據達到了這個日期的分片後循環從開始分片插入 #sPartionDay :分區天數,即默認從開始日期算起,分隔 2 天一個分區 #(3)重啓 Mycat #(4)訪問Mycat創建表 #用戶信息表 CREATE TABLE login_info ( `id` INT AUTO_INCREMENT comment '編號', `user_id` INT comment '用戶編號', `login_date` date comment '登錄日期', PRIMARY KEY(id) ); #(6)插入數據 INSERT INTO login_info(id,user_id,login_date) VALUES (1,101,'2019-01-01'); INSERT INTO login_info(id,user_id,login_date) VALUES (2,102,'2019-01-02'); INSERT INTO login_info(id,user_id,login_date) VALUES (3,103,'2019-01-03'); INSERT INTO login_info(id,user_id,login_date) VALUES (4,104,'2019-01-04'); INSERT INTO login_info(id,user_id,login_date) VALUES (5,103,'2019-01-05'); INSERT INTO login_info(id,user_id,login_date) VALUES (6,104,'2019-01-06'); #(7)查詢Mycat、dn1、dn2可以看到數據分片效果 |
全局序列
在實現分庫分表的情況下,數據庫自增主鍵已無法保證自增主鍵的全局唯一。爲此,Mycat 提供了全局 sequence,並且提供了包含本地配置和數據庫配置等多種實現方式
1.本地文件
此方式 Mycat 將 sequence 配置到文件中,當使用到 sequence 中的配置後,Mycat 會更下classpath 中的 sequence_conf.properties 文件中 sequence 當前的值。
① 優點:本地加載,讀取速度較快
② 缺點:抗風險能力差,Mycat 所在主機宕機後,無法讀取本地文件。
2、 數據庫方式
利用數據庫一個表 來進行計數累加。但是並不是每次生成序列都讀寫數據庫,這樣效率太低。Mycat 會預加載一部分號段到 Mycat 的內存中,這樣大部分讀寫序列都是在內存中完成的。如果內存中的號段用完了 Mycat 會再向數據庫要一次。
問:那如果 Mycat 崩潰了 ,那內存中的序列豈不是都沒了?
答:是的。如果是這樣,那麼 Mycat 啓動後會向數據庫申請新的號段,原有號段會棄用。也就是說如果 Mycat 重啓,那麼損失是當前的號段沒用完的號碼,但是不會因此出現主鍵重複
① 建庫序列腳本(host79/root)
#在 dn1 上創建全局序列表 CREATE TABLE MYCAT_SEQUENCE (NAME VARCHAR(50) NOT NULL,current_value INT NOT NULL,increment INT NOT NULL DEFAULT 100, PRIMARY KEY(NAME)) ENGINE=INNODB;
#創建全局序列所需函數 DELIMITER $$ CREATE FUNCTION mycat_seq_currval(seq_name VARCHAR(50)) RETURNS VARCHAR(64) DETERMINISTIC BEGIN DECLARE retval VARCHAR(64); SET retval="-999999999,null"; SELECT CONCAT(CAST(current_value AS CHAR),",",CAST(increment AS CHAR)) INTO retval FROM MYCAT_SEQUENCE WHERE NAME = seq_name; RETURN retval; END $$ DELIMITER ;
DELIMITER $$ CREATE FUNCTION mycat_seq_setval(seq_name VARCHAR(50),VALUE INTEGER) RETURNS VARCHAR(64) DETERMINISTIC BEGIN UPDATE MYCAT_SEQUENCE SET current_value = VALUE WHERE NAME = seq_name; RETURN mycat_seq_currval(seq_name); END $$ DELIMITER ;
DELIMITER $$ CREATE FUNCTION mycat_seq_nextval(seq_name VARCHAR(50)) RETURNS VARCHAR(64) DETERMINISTIC BEGIN UPDATE MYCAT_SEQUENCE SET current_value = current_value + increment WHERE NAME = seq_name; RETURN mycat_seq_currval(seq_name); END $$ DELIMITER ;
#初始化序列表記錄 INSERT INTO MYCAT_SEQUENCE(NAME,current_value,increment) VALUES ('ORDERS', 400000,100); |
② 修改 Mycat 配置
#修改sequence_db_conf.properties vim sequence_db_conf.properties #意思是 ORDERS這個序列在dn1這個節點上,具體dn1節點是哪臺機子,請參考schema.xml |
#修改server.xml vim server.xml #全局序列類型:0-本地文件,1-數據庫方式,2-時間戳方式。此處應該修改成1 #重啓Mycat |
③ 驗證全局序列
#登錄 Mycat,插入數據 insert into orders(id,amount,customer_id,order_type) values(next value for MYCATSEQ_ORDERS,1000,101,102); |
#查詢數據
#重啓Mycat後,再次插入數據,再查詢 |
3.時間戳方式
全局序列ID= 64 位二進制 (42(毫秒)+5(機器 ID)+5(業務編碼)+12(重複累加) 換算成十進制爲 18 位數的long 類型,每毫秒可以併發 12 位二進制的累加。
① 優點:配置簡單
② 缺點:18 位 ID 過長
4、 自主生成全局序列
可在 java 項目裏自己生成全局序列,如下:
① 根據業務邏輯組合
② 可以利用 redis 的單線程原子性 incr 來生成序列但,自主生成需要單獨在工程中用 java 代碼實現,還是推薦使用 Mycat 自帶全局序列。