之前我們已經講解過了數據的切分,主要有兩種方式,分別是垂直切分和水平切分,所謂的垂直切分就是將不同的表分布在不同的數據庫實例中,而水平切分指的是將一張表的數據按照不同的切分規則切分在不同實例的相同名稱的表中。
下面先來描述mycat的分庫操作,在進行分庫操作的時候需要注意一點:有關聯關系的表應該放在一個庫里,相互沒有關聯關系的表可以分到不同的庫中。
數據文件
--客戶表
CREATE TABLE customer(
id INT AUTO_INCREMENT,
NAME VARCHAR(200),
PRIMARY KEY(id)
);
--訂單表
CREATE TABLE orders(
id INT AUTO_INCREMENT,
order_type INT,
customer_id INT,
amount DECIMAL(10,2),
PRIMARY KEY(id)
);
--訂單詳細表
CREATE TABLE orders_detail(
id INT AUTO_INCREMENT,
detail VARCHAR(2000),
order_id INT,
PRIMARY KEY(id)
);
--訂單狀態字典表
CREATE TABLE dict_order_type(
id INT AUTO_INCREMENT,
order_type VARCHAR(200),
PRIMARY KEY(id)
);
1、分庫實戰
在上述的數據文件中,我們包含四個不同的表,現在將不同的表分布在不同的庫中,但是訪問的時候使用的是同一個mycat的終端,這些操作其實很簡單,都是由mycat來完成的,我們需要做的事情就是修改幾個簡單的配置即可。
1、修改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>
<writeHost host="hostM1" url="192.168.85.111:3306" user="root"
password="123456">
</writeHost>
</dataHost>
<dataHost name="host2" maxCon="1000" minCon="10" balance="0" writeType="0" dbType="mysql" dbDriver="native" switchType="1" sla
veThreshold="100"> <heartbeat>select user()</heartbeat>
<writeHost host="hostM2" url="192.168.85.113:3306" user="root" password="123456"></writeHost>
</dataHost>
</mycat:schema>
2、在兩個數據節點上分別創建orders數據庫
3、啟動mycat的服務
mysql -uroot -p123456 -h 192.168.85.111 -P8066
4、插入sql語句
執行上述的建表語句,會發現,customer在node03上,而其他的表在node01上,此時完成了分庫的功能。
在插入完成之后會發現,有的表名是大寫的,有的表名是大寫的,大家需要注意,這個是一個小問題,需要在mysql的配置文件中添加lower_case_table_names=1參數,來保證查詢的時候能夠進行正常的查詢。
2、分表實戰
相對於垂直拆分,水平拆分不是將表做分類,而是按照某個字段的某種規則來分散到多個庫之中,每個表中 包含一部分數據。簡單來說,我們可以將數據的水平切分理解為是按照數據行的切分,就是將表中的某些行切分 到一個數據庫,而另外的某些行又切分到其他的數據庫中 。
1、拆分orders表,在進行拆分的時候必須要指定拆分規則,在此實例中,我們選擇把customer_id按照取模運算進行數據拆分
1、修改schema.xml文件
<schema name="TESTDB" checkSQLschema="false" sqlMaxLimit="100
" dataNode="dn1"> <table name = "customer" dataNode="dn2"></table>
<table name ="orders" dataNode="dn1,dn2" rule="mod_ru
le"></table> </schema>
2、修改rule.xml文件
<tableRule name="mod_rule">
<rule>
<columns>customer_id</columns>
<algorithm>mod-long</algorithm>
</rule>
</tableRule>
<function name="mod-long" class="io.mycat.route.function.Part
itionByMod"> <!-- how many data nodes -->
<property name="count">2</property>
</function>
3、在node03上創建新的orders表
4、重啟mycat
5、插入數據,實現分片
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);
6、查看結果,分片成功。
3、mycat ER表的分片
在mycat中,我們已經將orders進行了數據分片,但是orders表跟orders_detail發生關聯,如果只把orders_detail放到一個分片上,那么跨庫的join很麻煩,所以提出了ER關系的表分片。什么意思呢?就是通過關聯關系,將子表與父表關聯的記錄放在同一個數據分片上。
1、修改schema.xml文件
<table name ="orders" dataNode="dn1,dn2" rule="mod_rule">
<childTable name="orders_detail" primaryKey="id" joinKey="order_id" parentKey="id" /> </table>
2、在node03上創建orders_detail表
3、重啟mycat
4、插入數據
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);
5、執行join操作
select * from orders o join orders_detail od on o.id = od.order_id;
4、全局表
在分片的情況下,當業務表因為規模而進行分片之后,業務表與這個字典表的之間關聯會變得比較棘手,因此,在mycat中存在一種全局表,他具備以下特性:
1、全局表的插入、更新操作會實時的在所有節點上執行,保持各個分片的數據一致性
2、全局表的查詢操作,只從一個節點獲取
3、全局表可以跟任何一個表進行join操作
實際操作:
1、修改schema.xml文件
<table name="dict_order_type" dataNode="dn1,dn2" type="global"></table>
2、向node03,node01添加dict_order_type表
3、重啟mycat
4、向表中插入數據
INSERT INTO dict_order_type(id,order_type) VALUES(101,'type1');
INSERT INTO dict_order_type(id,order_type) VALUES(102,'type2');
5、在各個節點進行數據查詢,發現都能查詢到對應的結果。
5、常用分片規則
1、取模運算(之前演示過)
2、分片枚舉
通過在配置文件中配置可能存在的值,配置分片。
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>
<!--
columns:分片字段, algorithm:分片函數
mapFile: 標識配置文件名稱, type: 0為int型、 非0為String,
defaultNode: 默認節點:小於 0 表示不設置默認節點,大於等於 0 表示設置默認節點,
設置默認節點如果碰到不識別的枚舉值,就讓它路由到默認節點,如不設置不識別就報錯
-->
<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>
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)
);
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');
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>
<!--
columns:分片字段, algorithm:分片函數
mapFile: 標識配置文件名稱, type: 0為int型、 非0為String,
defaultNode: 默認節點:小於 0 表示不設置默認節點,大於等於 0 表示設置默認節點,
設置默認節點如果碰到不識別的枚舉值,就讓它路由到默認節點,如不設置不識別就報錯
-->
<function name="rang-long"
class="io.mycat.route.function.AutoPartitionByLong">
<property name="mapFile">autopartition-long.txt</property>
<property name="defaultNode">0</property>
</function>
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)
);
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);
4、范圍求模算法
該算法為先進行范圍分片,計算出分片組,組內再求模,綜合了范圍分片和求模分片的優點。分片組內使用求模可以保證組內的數據分步比較均勻,分片組之間采用范圍分片可以兼顧范圍分片的特點。事先規定好分片的數量,數據擴容時按分片組擴容,則原有分片組的數據不需要遷移。由於分片組內的數據分步比較均勻,所以分片組內可以避免熱點數據問題。
1、在node01,node03創建表person
CREATE TABLE `person` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(50) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
2、修改schema.xml文件
<table name="person" primaryKey="id" dataNode="dn1,dn2" rule="auto-sharding-rang-mod"
></table>
3、修改rule.xml
<tableRule name="auto-sharding-rang-mod">
<rule>
<columns>id</columns>
<algorithm>rang-mod</algorithm>
</rule>
</tableRule>
<function name="rang-mod" class="io.mycat.route.function.PartitionByRangeMod">
<property name="mapFile">partition-range-mod.txt</property>
<property name="defaultNode">0</property>
</function>
4、修改partition-range-mod.txt文件
# range start-end ,data node group size
0-1M=1
1M1-2M=1
5、重啟mycat服務
6、插入如下數據
insert into person(id,name) values(9999,'zhangsan1');
insert into person(id,name) values(10000,'zhangsan2');
insert into person(id,name) values(10001,'zhangsan3');
insert into person(id,name) values(20000,'zhangsan4');
7、當插入的數據范圍超過我們給定的范圍之后,會插入到默認節點中
insert into person(id,name) values(25000,'zhangsan0');
當使用這種分片規則的時候,還可以進行擴容操作,
1、在node02上創建新的數據庫test
create database test;
2、創建person表
CREATE TABLE `person` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(50) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
3、修改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>
<table name ="orders" dataNode="dn1,dn2" rule="mod_rule">
<childTable name="orders_detail" primaryKey="id" joinKey="order_id" parentKey
="id" /> </table>
<table name="dict_order_type" dataNode="dn1,dn2" type="global"></table>
<table name="person" primaryKey="id" dataNode="dn1,dn2,dn3" rule="auto-sharding-rang-
mod"></table> </schema>
<dataNode name="dn1" dataHost="host1" database="orders" />
<dataNode name="dn2" dataHost="host2" database="orders"/>
<dataNode name="dn3" dataHost="host3" database="test"/>
<dataHost name="host1" maxCon="1000" minCon="10" balance="0"
writeType="0" dbType="mysql" dbDriver="native" switchType="1" slaveThresho
ld="100"> <heartbeat>select user()</heartbeat>
<writeHost host="hostM1" url="192.168.85.111:3306" user="root"
password="123456">
</writeHost>
</dataHost>
<dataHost name="host2" maxCon="1000" minCon="10" balance="0" writeType="0" dbType="mysql" dbD
river="native" switchType="1" slaveThreshold="100"> <heartbeat>select user()</heartbeat> <writeHost host="hostM2" url="192.168.85.113:3306" user="root" password="123456"></wr
iteHost> </dataHost>
<dataHost name="host3" maxCon="1000" minCon="10" balance="0" writeType="0" dbType="mysql" dbD
river="native" switchType="1" slaveThreshold="100"> <heartbeat>select user()</heartbeat> <writeHost host="hostM3" url="192.168.85.112:3306" user="root" password="123456"></wr
iteHost> </dataHost>
</mycat:schema>
4、修改partition-range-mod.txt文件
0-1M=2
1M1-2M=1
5、重啟mycat服務
6、插入如下sql語句
insert into person(id,name) values(9999,'zhangsan1');
insert into person(id,name) values(10000,'zhangsan2');
insert into person(id,name) values(10001,'zhangsan3');
insert into person(id,name) values(20000,'zhangsan4');
可以看到9999和1000被分配到不同的節點上了。
5、固定分片hash算法
類似於十進制的求模運算,但是為二進制的操作,取id的二進制低10位,即id二進制&1111111111.
此算法的優點在於如果按照十進制取模運算,則在連續插入110時,110會被分到1~10個分片,增大了插入事務的控制難度。而此算法根據二進制則可能會分到連續的分片,降低了插入事務的控制難度。
1、在node01,node02,node03創建表user
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(50) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
2、修改schema.xml文件
<table name="user" primaryKey="id" dataNode="dn1,dn2,dn3" rule="rule1"></table>
3、修改rule.xml
<!--
columns標識將要分片的表字段
algorithm為分片函數
partitionCount為分片個數列表
partitionLength為分片范圍列表,分區長度默認最大為1024,即最大支持1024個分區
約束如下:
count,length 兩個數組的長度必須是一致的。
1024 = sum((count[i]*length[i])). count 和 length 兩個向量的點積恆等於 1024
-->
<tableRule name="rule1">
<rule>
<columns>id</columns>
<algorithm>func1</algorithm>
</rule>
</tableRule>
<function name="func1" class="io.mycat.route.function.PartitionByLong">
<property name="partitionCount">2,1</property>
<property name="partitionLength">256,512</property>
</function>
4、重啟mycat服務
5、插入如下數據
insert into user(id,name) values(1023,'zhangsan1');
insert into user(id,name) values(1024,'zhangsan2');
insert into user(id,name) values(266,'zhangsan3');
insert into user(id,name) values(255,'zhangsan4');
可以看到結果,1024,255在一個數據分片,266在一個數據分片,1023在一個數據分片。
6、取模范圍算法
取模運算與范圍約束的結合主要是為后續的數據遷移做准備,即可以自主決定取模后數據的節點分布。
1、在node01,node02,node03創建表user2
CREATE TABLE `user2` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(50) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
2、修改schema.xml文件
<table name="user2" primaryKey="id" dataNode="dn1,dn2,dn3" rule="sharding-by-pattern"
></table>
3、修改rule.xml
<!--
columns標識將要分片的表字段
algorithm為分片函數
mapFile:切分規則配置文件
patternValue:求模基數
defaultNode:默認節點,小於0表示不設置默認節點,大於等於0表示設置默認節點,如果超出配置的范圍,則使用默認節點;
-->
<tableRule name="sharding-by-pattern">
<rule>
<columns>id</columns>
<algorithm>sharding-by-pattern</algorithm>
</rule>
</tableRule>
<function name="sharding-by-pattern" class="io.mycat.route.function.PartitionByPattern">
<property name="mapFile">partition-pattern.txt</property>
<property name="patternVAlue">256</property>
<property name="defaultNode">0</property>
</function>
4、修改partition-pattern.txt文件
0-86=0
87-173=1
174-256=2
5、重啟mycat服務
6、插入如下數據
insert into user2(id,name) values(85,'zhangsan1');
insert into user2(id,name) values(100,'zhangsan2');
insert into user2(id,name) values(200,'zhangsan3');
可以看到結果不同的記錄被分散到不同的數據分片上。
7、字符串hash求模范圍算法
與取模范圍算法類似,該算法支持數值、符號、字母取模,此方式就是將指定位數的字符的ascll碼的和進行取模運算,配置如下:
1、在node01,node02,node03創建表user3
CREATE TABLE `user3` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(50) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
2、修改schema.xml文件
<table name="user3" primaryKey="id" dataNode="dn1,dn2,dn3" rule="sharding-by-prefixpattern"
></table>
3、修改rule.xml
<!--
columns標識將要分片的表字段
algorithm為分片函數
mapFile:切分規則配置文件
patternValue:求模基數
prefixLength:ASCII 截取的位數
-->
<tableRule name="sharding-by-prefixpattern">
<rule>
<columns>name</columns>
<algorithm>sharding-by-prefixpattern</algorithm>
</rule>
</tableRule>
<function name="sharding-by-prefixpattern" class="io.mycat.route.function.PartitionByPrefixPattern">
<property name="mapFile">partition-pattern.txt</property>
<property name="patternVAlue">256</property>
<property name="prefixLength">5</property>
</function>
4、修改partition-pattern.txt文件
# ASCII
# 8-57=0-9 阿拉伯數字
# 64、 65-90=@、 A-Z
# 97-122=a-z
0-86=0
87-173=1
174-256=2
5、重啟mycat服務
6、插入如下數據
insert into user3(id,name) values(1,'zhangsan');
insert into user3(id,name) values(2,'lisi');
insert into user3(id,name) values(3,'wangwu');
insert into user3(id,name) values(4,'zzzzzzz');
insert into user3(id,name) values(5,'z99');
可以看到數據被分散了,但是因為ascll碼值是需要計算的,所以結果可能不是很明顯,大家需要自己的拼字符串來試驗。
8、應用指定的算法
在運行階段由應用程序自主決定路由到哪個分片,配置如下:
1、在node01,node02,node03創建表user4
CREATE TABLE `user4` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(50) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
2、修改schema.xml文件
<table name="user4" primaryKey="id" dataNode="dn1,dn2,dn3" rule="sharding-by-substring"
></table>
3、修改rule.xml
<!--
columns標識將要分片的表字段
algorithm為分片函數
-->
<tableRule name="sharding-by-substring">
<rule>
<columns>name</columns>
<algorithm>sharding-by-substring</algorithm>
</rule>
</tableRule>
<function name="sharding-by-substring" class="io.mycat.route.function.PartitionDirectBySubString">
<property name="startIndex">0</property>
<property name="size">1</property>
<property name="partitionCount">3</property>
<property name="defaultPartition">0</property>
</function>
4、重啟mycat服務
5、插入如下數據
insert into user4(id,name) values(1,'0-zhangsan');
insert into user4(id,name) values(2,'1-lisi');
insert into user4(id,name) values(3,'2-wangwu');
可以看到數據被分片存儲了。
9、字符串hash解析算法
字符串hash解析分片,其實就是根據配置的hash預算位規則,將截取的字符串進行hash計算后,得到的int數值即為datanode index(分片節點索引,從0開始)。
1、在node01,node02,node03創建表user5
CREATE TABLE `user5` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(50) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
2、修改schema.xml文件
<table name="user5" primaryKey="id" dataNode="dn1,dn2,dn3" rule="sharding-by-stringhash"
></table>
3、修改rule.xml
<!--
columns標識將要分片的表字段
algorithm為分片函數
-->
<tableRule name="sharding-by-stringhash">
<rule>
<columns>id</columns>
<algorithm>sharding-by-stringhash</algorithm>
</rule>
</tableRule>
<!--
partitionLength:求模基數
partitionCount:分片數量
hashslice: hash 預算位,即根據子字符串中 int 值 hash 運算
hashSlice : 0 means str.length(), -1 means str.length()-1
/**
* “2” -> (0,2)
* “1:2” -> (1,2)
* “1:” -> (1,0)
* “-1:” -> (-1,0)
* “:-1” -> (0,-1)
* “:” -> (0,0)
*/
-->
<function name="sharding-by-stringhash" class="io.mycat.route.function.PartitionByString">
<property name="partitionLength">512</property>
<property name="partitionCount">3</property>
<property name="hashSlice">0:6</property>
</function>
4、重啟mycat服務
5、插入如下數據
insert into user5(id,name) values(1111111,database());
insert into user5(id,name) values(2222222,database());
insert into user5(id,name) values(3333333,database());
insert into user5(id,name) values(4444444,database());
insert into user5(id,name) values(8960000,database());
10、按照日期范圍分片
按照某個指定的日期進行分片
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>
<!--
columns:分片字段, algorithm:分片函數
dateFormat :日期格式
sBeginDate :開始日期
sEndDate:結束日期,則代表數據達到了這個日期的分片后循環從開始分片插入
sPartionDay :分區天數,即默認從開始日期算起,分隔 2 天一個分區
-->
<function name="shardingByDate" class="io.mycat.route.function.PartitionByDate">
<property name="dateFormat">yyyy-MM-dd</property>
<property name="sBeginDate">2020-06-01</property>
<property name="sEndDate">2020-06-04</property>
<property name="sPartionDay">2</property>
</function>
3、重啟mycat服務
4、創建mycat表
CREATE TABLE login_info
(
`id` INT AUTO_INCREMENT comment '編號',
`user_id` INT comment '用戶編號',
`login_date` date comment '登錄日期',
PRIMARY KEY(id)
);
INSERT INTO login_info(id,user_id,login_date) VALUES (1,101,'2020-06-01');
INSERT INTO login_info(id,user_id,login_date) VALUES (2,102,'2020-06-02');
INSERT INTO login_info(id,user_id,login_date) VALUES (3,103,'2020-06-03');
INSERT INTO login_info(id,user_id,login_date) VALUES (4,104,'2020-06-04');
INSERT INTO login_info(id,user_id,login_date) VALUES (5,103,'2020-06-05');
INSERT INTO login_info(id,user_id,login_date) VALUES (6,104,'2020-06-06');
11、按單月小時分片
此規則是單月內按照小時拆分,最小粒度是小時,可以一天最多24個分片,最少一個分片,一個月完成后下個月開始循環,每個月月尾,需要手工清理數據。
1、修改schema.xml文件
<table name="user6" dataNode="dn1,dn2,dn3" rule="sharding-by-hour"></table>
2、修改rule.xml文件
<tableRule name="sharding-by-hour">
<rule>
<columns>create_time</columns>
<algorithm>sharding-by-hour</algorithm>
</rule>
</tableRule>
<!--
columns: 拆分字段,字符串類型(yyyymmddHH)
splitOneDay : 一天切分的分片數
-->
<function name="sharding-by-hour" class="io.mycat.route.function.LatestMonthPartion">
<property name="splitOneDay">3</property>
</function>
3、重啟mycat服務
4、創建mycat表
create table user6(
id int not null,
name varchar(64),
create_time varchar(10)
);
insert into user6(id,name,create_time) values(1,'steven','2020060100');
insert into user6(id,name,create_time) values(1,'steven','2020060101');
insert into user6(id,name,create_time) values(1,'steven','2020060102');
insert into user6(id,name,create_time) values(1,'steven','2020060103');
insert into user6(id,name,create_time) values(1,'steven','2020060104');
insert into user6(id,name,create_time) values(1,'steven','2020060105');
insert into user6(id,name,create_time) values(1,'steven','2020060106');
insert into user6(id,name,create_time) values(1,'steven','2020060107');
insert into user6(id,name,create_time) values(1,'steven','2020060108');
insert into user6(id,name,create_time) values(1,'steven','2020060109');
insert into user6(id,name,create_time) values(1,'steven','2020060110');
insert into user6(id,name,create_time) values(1,'steven','2020060111');
insert into user6(id,name,create_time) values(1,'steven','2020060112');
insert into user6(id,name,create_time) values(1,'steven','2020060113');
insert into user6(id,name,create_time) values(1,'steven','2020060114');
insert into user6(id,name,create_time) values(1,'steven','2020060115');
insert into user6(id,name,create_time) values(1,'steven','2020060116');
insert into user6(id,name,create_time) values(1,'steven','2020060117');
insert into user6(id,name,create_time) values(1,'steven','2020060118');
insert into user6(id,name,create_time) values(1,'steven','2020060119');
insert into user6(id,name,create_time) values(1,'steven','2020060120');
insert into user6(id,name,create_time) values(1,'steven','2020060121');
insert into user6(id,name,create_time) values(1,'steven','2020060122');
insert into user6(id,name,create_time) values(1,'steven','2020060123');
insert into user6(id,name,create_time) values(1,'steven','2020060200');
insert into user6(id,name,create_time) values(1,'steven','2020060201');
insert into user6(id,name,create_time) values(1,'steven','2020060202');
insert into user6(id,name,create_time) values(1,'steven','2020060203');
insert into user6(id,name,create_time) values(1,'steven','2020060204');
insert into user6(id,name,create_time) values(1,'steven','2020060205');
insert into user6(id,name,create_time) values(1,'steven','2020060206');
insert into user6(id,name,create_time) values(1,'steven','2020060207');
insert into user6(id,name,create_time) values(1,'steven','2020060208');
insert into user6(id,name,create_time) values(1,'steven','2020060209');
insert into user6(id,name,create_time) values(1,'steven','2020060210');
insert into user6(id,name,create_time) values(1,'steven','2020060211');
當運行完成之后會發現,第一天的數據能夠正常的插入成功,均勻的分散到3個分片上,但是第二天的數據就無法成功分散了,原因就在於我們的數據分片不夠,所以這種方式幾乎沒有人使用。
12、日期范圍hash分片
思想與范圍求模一致,當由於日期在取模會有數據集中問題,所以改成了hash方法。先根據時間hash使得短期內數據分布的更均勻,有點可以避免擴容時的數據遷移,又可以一定程度上避免范圍分片的熱點問題,要求日期格式盡量精確,不然達不到局部均勻的目的。
1、修改schema.xml文件
<table name="user7" dataNode="dn1,dn2,dn3" rule="rangeDateHash"></table>
2、修改rule.xml文件
<tableRule name="rangeDateHash">
<rule>
<columns>create_time</columns>
<algorithm>range-date-hash</algorithm>
</rule>
</tableRule>
<!--
columns: 拆分字段,字符串類型(yyyymmddHH)
algorithm:分片函數
sBeginDate:指定開始的日期,與dateFormat格式一致
sPartionDay:代表多少天一組
dateFormat:指定的日期格式,符合java標准
-->
<function name="range-date-hash"
class="io.mycat.route.function.PartitionByRangeDateHash">
<property name="sBeginDate">2020-06-01 00:00:00</property>
<property name="sPartionDay">3</property>
<property name="dateFormat">yyyy-MM-dd HH:mm:ss</property>
<property name="groupPartionSize">1</property>
</function>
3、重啟mycat服務
4、創建mycat表
create table user7(
id int not null,
name varchar(64),
create_time varchar(20)
);
insert into user7(id,name,create_time) values(1,'steven','2020-06-01 00:00:00');
insert into user7(id,name,create_time) values(1,'steven','2020-06-02 00:00:00');
insert into user7(id,name,create_time) values(1,'steven','2020-06-03 00:00:00');
insert into user7(id,name,create_time) values(1,'steven','2020-06-04 00:00:00');
insert into user7(id,name,create_time) values(1,'steven','2020-06-05 00:00:00');
insert into user7(id,name,create_time) values(1,'steven','2020-06-06 00:00:00');
insert into user7(id,name,create_time) values(1,'steven','2020-06-07 00:00:00');
insert into user7(id,name,create_time) values(1,'steven','2020-06-08 00:00:00');
insert into user7(id,name,create_time) values(1,'steven','2020-06-09 00:00:00');
insert into user7(id,name,create_time) values(1,'steven','2020-06-10 00:00:00');
insert into user7(id,name,create_time) values(1,'steven','2020-06-11 00:00:00');
通過結果也可以看出,每三天一個分片,那么我們只有三個數據節點,所以到10號的數據的時候,沒有辦法進行數據的插入了,原因就在於沒有足夠多的數據節點。
13、冷熱數據分片
根據日期查詢冷熱數據分布,最近n個月的到實時交易庫查詢,其他的到其他庫中
1、修改schema.xml文件
<table name="user8" dataNode="dn1,dn2,dn3" rule="sharding-by-hotdate" />
2、修改rule.xml文件
<tableRule name="sharding-by-hotdate">
<rule>
<columns>create_time</columns>
<algorithm>sharding-by-hotdate</algorithm>
</rule>
</tableRule>
<!--
dataFormat:時間格式化
sLastDay:熱數據的天數
sPartionDay:冷數據的分片天數(按照天數分片)
-->
<function name="sharding-by-hotdate" class="io.mycat.route.function.PartitionByHotDate">
<property name="dateFormat">yyyy-MM-dd</property>
<property name="sLastDay">10</property>
<property name="sPartionDay">30</property>
</function>
3、重啟mycat服務
4、創建mycat表
CREATE TABLE user8(create_time timestamp NULL ON UPDATE CURRENT_TIMESTAMP ,`db_nm` varchar(20) NULL);
INSERT INTO user8 (create_time,db_nm) VALUES ('2020-06-01', database());
INSERT INTO user8 (create_time,db_nm) VALUES ('2020-06-10', database());
INSERT INTO user8 (create_time,db_nm) VALUES ('2020-06-11', database());
INSERT INTO user8 (create_time,db_nm) VALUES ('2020-06-21', database());
INSERT INTO user8 (create_time,db_nm) VALUES ('2020-06-30', database());
INSERT INTO user8 (create_time,db_nm) VALUES ('2020-07-30', database());
14、自然月分片
1、修改schema.xml文件
<table name="user9" dataNode="dn1,dn2,dn3" rule="sharding-by-month" />
2、修改rule.xml文件
<tableRule name="sharding-by-month">
<rule>
<columns>create_time</columns>
<algorithm>sharding-by-month</algorithm>
</rule>
</tableRule>
<!--
columns: 分片字段,字符串類型
dateFormat : 日期字符串格式
sBeginDate : 開始日期
-->
<function name="sharding-by-month" class="io.mycat.route.function.PartitionByMonth">
<property name="dateFormat">yyyy-MM-dd</property>
<property name="sBeginDate">2019-01-01</property>
</function>
3、重啟mycat服務
4、創建mycat表
CREATE TABLE user9(id int,name varchar(10),create_time varchar(20));
insert into user9(id,name,create_time) values(111,'zhangsan','2019-01-01');
insert into user9(id,name,create_time) values(111,'zhangsan','2019-03-01');
insert into user9(id,name,create_time) values(111,'zhangsan','2019-05-01');
insert into user9(id,name,create_time) values(111,'zhangsan','2019-07-01');
insert into user9(id,name,create_time) values(111,'zhangsan','2019-09-01');
insert into user9(id,name,create_time) values(111,'zhangsan','2019-11-01');
15、一致性hash分片
實現方式:一致性hash分片,利用一個分片節點對應一個或者多個虛擬hash桶的思想,,盡可能減少分片擴展時的數據遷移。
優點:有效解決了分布式數據庫的擴容問題。
缺點:在橫向擴展的時候,需要遷移部分數據;由於虛擬桶倍數與分片節點數都必須是正整數,而且要服從"虛擬桶倍數×分片節點數=設計極限",因此在橫向擴容的過程中,增加分片節點並不是一台一台地加上去的,而是以一種因式分解的方式增加,因此有浪費物理計算力的可能性。
1、修改schema.xml文件
<table name="user10" dataNode="dn1,dn2,dn3" primaryKey="id" rule="sharding-by-murmur" />
2、修改rule.xml文件
<tableRule name="sharding-by-murmur">
<rule>
<columns>id</columns>
<algorithm>murmur</algorithm>
</rule>
</tableRule>
<function name="murmur" class="io.mycat.route.function.PartitionByMurmurHash">
<property name="seed">0</property><!-- 默認是 0-->
<property name="count">2</property><!-- 要分片的數據庫節點數量,必須指定,否則沒法分片-->
<property name="virtualBucketTimes">160</property>
<!-- 一個實際的數據庫節點被映射為這么多虛擬節點,默認是 160 倍,也就是虛擬節點數是物理節點數的 160 倍-->
<!--<property name="weightMapFile">weightMapFile</property>
節點的權重,沒有指定權重的節點默認是 1。以 properties 文件的格式填寫,以從 0 開始到 count-1 的整數值也就是節點索引為 key,以節點權重值為值。所有權重值必須是正整數,否則以 1 代替 -->
<property name="bucketMapPath">/etc/mycat/bucketMapPath</property>
<!--用於測試時觀察各物理節點與虛擬節點的分布情況,如果指定了這個屬性,會把虛擬節點的 murmur hash 值與物理節點的映射按行輸出到這個文件,沒有默認值,如果不指定,就不會輸出任何東西 -->
</function>
3、重啟mycat服務
4、創建mycat表
create table user10(id bigint not null primary key,name varchar(20));
insert into user10(id,name) values(1111111,database());
insert into user10(id,name) values(2222222,database());
insert into user10(id,name) values(3333333,database());
insert into user10(id,name) values(4444444,database());
insert into user10(id,name) values(8960000,database());
6、mycat的分片join
Join絕對是關系型數據庫中最常用的一個特性,然而在分布式環境中,跨分配的join卻是最復雜的,最難解決的一個問題。
性能建議:
1、盡量避免使用left join或right join,而用inner join
2、在使用left join或right join時,on會優先執行,where條件在最后執行,所以再使用過程中,條件盡可能的在on語句中判斷,減少where的執行
3、少使用子查詢,而用join
mycat目前版本支持跨分配的join,主要有四種實現方式
1、全局表
2、ER分片
3、catletT(人工智能)
4、ShareJoin
全局表和ER分片在之前的操作中已經講過了,此處不再贅述,下面詳細講解下另外兩種方式。
Share join
ShareJoin是一個簡單的跨分片join,基於HBT的方式實現。目前支持2個表的join,原理是解析SQL語句,拆分成單表的SQL語句執行,然后把各個節點的數據匯集。
配置方式如下:
A,B的dataNode相同
<table name="A" dataNode="dn1,dn2,dn3" rule="auto-sharding-long" />
<table name="B" dataNode="dn1,dn2,dn3" rule="auto-sharding-long" />
A,B的dataNode不同
<table name="A" dataNode="dn1,dn2 " rule="auto-sharding-long" />
<table name="B" dataNode="dn1,dn2,dn3" rule="auto-sharding-long" />
------------
<table name="A" dataNode="dn1 " rule="auto-sharding-long" />
<table name="B" dataNode=" dn2,dn3" rule="auto-sharding-long" />
修改schema.xml
<table name="company" primaryKey="id" dataNode="dn1,dn2,dn3" rule="mod-long" />
<table name="customers" primaryKey="id" dataNode="dn1,dn2" rule="sharding-by-intfile"/>
修改rule.xml
<tableRule name="mod-long">
<rule>
<columns>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">3</property>
</function>
<tableRule name="sharding-by-intfile">
<rule>
<columns>sharding_id</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>
</function>
修改partition-hash-int.txt文件
10000=0
10010=1
sql語句:
create table company(id int primary key,name varchar(10)) engine=innodb;
insert company (id,name) values(1,'mycat');
insert company (id,name) values(2,'ibm');
insert company (id,name) values(3,'hp');
create table customers(id int not null primary key,name varchar(100),company_id int not null,sharding_id int not null);
insert into customers(id,name,company_id,sharding_id)values(1,'wang',1,10000),(2,'xue',2,10010),(3,'feng',3,10000);
驗證:
-- 可以看到有時可以查出對應的結果,有時則查詢不到
select a.*,b.ID,b.NAME as tit from customers a,company b where a.COMPANY_ID=b.ID;
--可以看到每次都可以直接查詢到結果
/*!mycat:catlet=io.mycat.catlets.ShareJoin */select a.*,b.ID,b.NAME as tit from customers a,company b where a.COMPANY_ID=b.ID;
--其他寫法
/*!mycat:catlet=io.mycat.catlets.ShareJoin */select a.*,b.ID,b.NAME as tit from customers a join company b on a.COMPANY_ID=b.ID;
/*!mycat:catlet=io.mycat.catlets.ShareJoin */select a.*,b.ID,b.NAME as tit from customers a join company b where a.COMPANY_ID=b.ID;
7、全局序列號
在實現分庫分表的情況下,數據庫自增主鍵已經無法保證自增主鍵的全局唯一,為此,mycat提供了全局sequence,並且提供了包含本地配置和數據庫配置等多種實現方式。
1、本地文件方式
使用此方式的時候,mycat講sequence配置到文件中,當使用到sequence中的配置,mycat會更新sequence_conf.properties文件中sequence當前的值。
配置方式:
在 sequence_conf.properties 文件中做如下配置:
GLOBAL_SEQ.HISIDS=
GLOBAL_SEQ.MINID=10001
GLOBAL_SEQ.MAXID=20000
GLOBAL_SEQ.CURID=10000
其中 HISIDS 表示使用過的歷史分段(一般無特殊需要可不配置), MINID 表示最小 ID 值, MAXID 表示最大
ID 值, CURID 表示當前 ID 值。
server.xml 中配置:
<system><property name="sequnceHandlerType">0</property></system>
注: sequnceHandlerType 需要配置為 0,表示使用本地文件方式。
案例使用:
create table tab1(id int primary key,name varchar(10));
insert into tab1(id,name) values(next value for mycatseq_global,'test1');
insert into tab1(id,name) values(next value for mycatseq_global,'test2');
insert into tab1(id,name) values(next value for mycatseq_global,'test3');
缺點:當mycat重新發布后,配置文件中的sequence會恢復到初始值
優點:本地加載,讀取速度較快
2、數據庫方式
在數據庫中建立一張表,存放sequence名稱(name),sequence當前值(current_value),步長(increment int類型,每次讀取多少個sequence,假設為K)等信息;
獲取數據步驟:
1、當初次使用該sequence時,根據傳入的sequence名稱,從數據庫這張表中讀取current_value和increment到mycat中,並將數據庫中的current_value設置為原current_value值+increment值。
2、mycat將讀取到current_value+increment作為本次要使用的sequence值,下次使用時,自動加1,當使用increment次后,執行步驟1中的操作
3、mycat負責維護這張表,用到哪些sequence,只需要在這張表中插入一條記錄即可,若某次讀取的sequence沒有用完,系統就停掉了,則這次讀取的sequence剩余值不會再使用
配置方式:
1、修改server.xml文件
<system><property name="sequnceHandlerType">1</property></system>
2、修改schema.xml文件
<table name="test" primaryKey="id" autoIncrement="true" dataNode="dn1,dn2,dn3" rule="mod-long"/>
<table name="mycat_sequence" primaryKey="name" dataNode="dn2"/>
3、修改mycat配置文件sequence_db_conf.properties,添加屬性值
#sequence stored in datanode
GLOBAL=dn1
COMPANY=dn1
CUSTOMER=dn1
ORDERS=dn1
MYCAT=dn2
4、在dn2上添加mycat_sequence表
DROP TABLE IF EXISTS mycat_sequence;
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;
5、在dn2上的mycat_sequence表中插入初始記錄
INSERT INTO mycat_sequence(name,current_value,increment) VALUES ('mycat', -99, 100);
6、在dn2上創建函數
--創建函數
DROP FUNCTION IF EXISTS mycat_seq_currval;
DELIMITER $
CREATE FUNCTION mycat_seq_currval(seq_name VARCHAR(50)) RETURNS varchar(64) CHARSET utf8
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 ;
--設置sequence值
DROP FUNCTION IF EXISTS mycat_seq_setval;
DELIMITER $
CREATE FUNCTION mycat_seq_setval(seq_name VARCHAR(50),value INTEGER) RETURNS varchar(64) CHARSET utf8
DETERMINISTIC
BEGIN
UPDATE mycat_sequence
SET current_value = value
WHERE name = seq_name;
RETURN mycat_seq_currval(seq_name);
END $
DELIMITER ;
--獲取下一個sequence值
DROP FUNCTION IF EXISTS mycat_seq_nextval;
DELIMITER $
CREATE FUNCTION mycat_seq_nextval(seq_name VARCHAR(50)) RETURNS varchar(64) CHARSET utf8
DETERMINISTIC
BEGIN
UPDATE mycat_sequence
SET current_value = current_value + increment WHERE name = seq_name;
RETURN mycat_seq_currval(seq_name);
END $
DELIMITER ;
數據測試:
1、插入數據表
create table test(id int,name varchar(10));
2、查詢對應的序列數據表
SELECT * FROM mycat_sequence;
3、向表中插入數據,可以多執行幾次
insert into test(id,name) values(next value for MYCATSEQ_MYCAT,(select database()));
4、查詢添加的數據
SELECT * FROM test order by id asc;
5、重新啟動mycat,重新添加數據,查看結果,重啟之后從101開始
SELECT * FROM mycat_sequence;
6、重新查詢數據表test
SELECT * FROM test order by id asc;
大家在使用的時候會發現報錯的情況,這個錯誤的原因不是因為我們的配置,是因為我們的版本問題,簡單替換下版本即可。
mysql> insert into test(id,name) values(next value for MYCATSEQ_MYCAT,(select database()));
ERROR 1003 (HY000): mycat sequnce err.java.lang.NumberFormatException: null
3、本地時間戳方式
ID= 64 位二進制 (42(毫秒)+5(機器 ID)+5(業務編碼)+12(重復累加)。
換算成十進制為 18 位數的 long 類型,每毫秒可以並發 12 位二進制的累加。
使用方式:
1、配置server.xml文件
<property name="sequnceHandlerType">2</property>
2、修改sequence_time_conf.properties
WORKID=06 #任意整數
DATAACENTERID=06 #任意整數
3、修改schema.xml文件
<table name="test2" dataNode="dn1,dn2,dn3" primaryKey="id" autoIncrement="true" rule="mod-long" />
4、啟動mycat,並且創建表進行測試
create table test2(id bigint auto_increment primary key,xm varchar(32));
insert into test2(id,xm) values(next value for MYCATSEQ_GLOBAL,'lisi') ;
此方式的優點是配置簡單,但是缺點也很明顯就是18位的id太長,需要耗費多余的存儲空間。
4、自定義全局序列
用戶還可以在程序中自定義全局序列,通過java代碼來實現,這種方式一般比較麻煩,因此在能使用mycat提供的方式滿足需求的前提下一般不需要自己通過java代碼來實現。
5、分布式ZK ID生成器
如果在搭建的時候使用了zookeeper,也可以使用zk來生成對應的id,此方式需要zk的配合,此處不再展示,有興趣的同學下去自己演示即可。
