0%

mycat使用

一、读写分离

​ 通过mycatmysql的主从复制配合搭建数据库的读写分离,可以实现mysql的高可用性。

修改mycat的schema.xml配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<?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">
</schema>
<dataNode name="dn1" dataHost="host1" database="my_multi_test" />
<!-- 修改负载策略
balance=1:全部的readhost和stand by writehost参与select 语句的负载均衡,简单的说,当双主双从模式下,其他的节点都参与select语句的负载均衡
-->
<dataHost name="host1" maxCon="1000" minCon="10" balance="1"
writeType="0" dbType="mysql" dbDriver="native" switchType="1" slaveThreshold="100">
<heartbeat>select user()</heartbeat>

<!--master-1-->
<writeHost host="hostM1" url="192.168.243.131:3306" user="root"
password="Root_123">
<!--slave-1-->
<readHost host="hostS1" url="192.168.243.132:3306" user="root" password="Root_123"></readHost>
</writeHost>

<!--master-2-->
<writeHost host="hostM2" url="192.168.243.133:3306" user="root"
password="Root_123">
<!--slave-2-->
<readHost host="hostS2" url="192.168.243.134:3306" user="root" password="Root_123"></readHost>
</writeHost>
</dataHost>
</mycat:schema>

writeType

​ 表示写操作发送到哪台机器

  • writeType=0:所有写操作都发送到配置的第一个writeHost,第一个挂了切换到还生存的第二个
  • writeType=1:所有写操作都随机的发送到配置的writehost中,1.5之后废弃

switchType

​ 表示如何进行切换

  • switchType=1:默认值,自动切换

  • switchType=-1:表示不自动切换

  • switchType=2:基于mysql主从同步的状态决定是否切换

验证

重启mycat restart

连接mycat mysql -uroot -pRoot_123 -P 8066 -h 192.168.243.131

执行插入语句 insert into t1 values(2,@@hostname);

分别连接四台mysql查询结果

连接mycat查询结果

到此,mysql双主双从结合mycat搭建读写分离完成,架构如下图所示

  • 当查询时,会被分发到slave-1master-2slave-2这三台机器上;
  • 当写操作时,会被分发到master-1上;
  • 如果master-1宕机,mycat会将写操作自动切换到master-2,之后如果master-1恢复(从master-2同步数据,slave-1再从master-1同步数据),也只提供读服务,写仍由master-2提供。

二、分库分表

​ 数据的切分,主要有两种方式,分别是垂直切分和水平切分,所谓的垂直切分就是将不同的表分布在不同的数据库实例中,而水平切分指的是将一张表的数据按照不同的切分规则切分在不同实例的相同名称的表中。

1、分库

将不同的表分布在不同的库中,但是访问的时候使用的是同一个mycat的终端,这些操作都由mycat来完成的,我们只需修改相应的配置即可。

1.1、修改/etc/my.cnf文件

​ 在mycat操作时它是不区分表明大小写的,需要在mysql的配置文件中添加lower_case_table_names=1参数,来保证查询的时候能够进行正常的查询。

1
lower_case_table_names=1

1.2、重启mysql

1
2
3
4
# 重启
service mysqld restart
# 查看状态
service mysqld status

1.3、修改schema.xml文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<?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">
<!-- 指定customer表分发到db2进行操作,其余未指定的表默认dn1 -->
<table name = "customer" dataNode="dn2"></table>
</schema>

<dataNode name="dn1" dataHost="host1" database="my_db" />
<dataNode name="dn2" dataHost="host2" database="my_db" />

<dataHost name="host1" maxCon="1000" minCon="10" balance="1"
writeType="0" dbType="mysql" dbDriver="native" switchType="1" slaveThreshold="100">
<heartbeat>select user()</heartbeat>

<!--master-1-->
<writeHost host="hostM1" url="192.168.243.131:3306" user="root" password="Root_123">
<!--slave-1-->
<readHost host="hostS1" url="192.168.243.132:3306" user="root" password="Root_123"></readHost>
</writeHost>
</dataHost>
<dataHost name="host2" maxCon="1000" minCon="10" balance="1"
writeType="0" dbType="mysql" dbDriver="native" switchType="1" slaveThreshold="100">

<heartbeat>select user()</heartbeat>
<!--master-2-->
<writeHost host="hostM2" url="192.168.243.133:3306" user="root" password="Root_123">
<!--slave-2-->
<readHost host="hostS2" url="192.168.243.134:3306" user="root" password="Root_123"></readHost>
</writeHost>
</dataHost>
</mycat:schema>

1.4、重启mycat

1
2
3
4
# 重启
mycat restart
# 查看状态
mycat status

1.5、停止master的slave服务

​ 使用mycat进行分库操作之前,需要分别登陆master-1master-2执行stop slave,将它们的主备关系去除,否则相互之间会进行数据同步,起不到分库的作用。

1
2
3
4
-- 停止slave服务
stop slave;
-- 查看状态
show slave status\G

登陆master-1master-2的mysql服务分别创建my_db数据库

1
create database my_db;

1.6、验证

登陆mcat
1
mysql -uroot -pRoot_123 -h 192.168.243.131 -P 8066
建表
1
2
3
4
5
CREATE TABLE customer(
id INT AUTO_INCREMENT,
NAME VARCHAR(200),
PRIMARY KEY(id)
);

由上图可以看到,mycat的建表语句被分发到了master-2执行,并且因为master-2slave-2主从同步,slave-2也出现了customer表。

插入数据
1
insert into customer values(1,'zhangsan');

由上图可以看到,mycat插入的语句被分发到了master-2执行,并且因为master-2slave-2主从同步,slave-2也出现了相关的数据。


2、分表(分片)

​ 相对于垂直拆分,水平拆分不是将表做分类,而是按照某个字段的某种规则来分散到多个库之中,每个表中 包含一部分数据。简单来说,我们可以将数据的水平切分理解为是按照数据行的切分,就是将表中的某些行切分 到一个数据库,而另外的某些行又切分到其他的数据库中 。

2.1、取模运算

把customer_id按照取模运算进行数据拆分

修改schema.xml文件

​ 在上文分库的schema文件基础上修改

1
2
3
4
<schema name="TESTDB" checkSQLschema="false" sqlMaxLimit="100" dataNode="dn1">		
<table name = "customer" dataNode="dn2"></table>
<table name ="orders" dataNode="dn1,dn2" rule="mod_rule"></table>
</schema>
修改rule.xml文件

​ 配置取模运算规则

1
2
3
4
5
6
7
8
9
10
<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>
重启mycat
建表
1
2
3
4
5
6
7
CREATE TABLE orders(
id INT AUTO_INCREMENT,
order_type INT,
customer_id INT,
amount DECIMAL(10,2),
PRIMARY KEY(id)
);
插入数据
1
2
3
4
5
6
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会将我们插入的数据根据customer_id字段值%2,结果为0的分发到master-1,为1分发master-2


2.2、分片枚举

通过在配置文件中配置可能存在的值,配置分片。

修改schema.xml文件
1
2
3
<schema name="TESTDB" checkSQLschema="false" sqlMaxLimit="100" dataNode="dn1">	
<table name="orders_ware_info" dataNode="dn1,dn2" rule="sharding_by_intfile" ></table>
</schema>
修改rule.xml文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<tableRule name="sharding_by_intfile">
<rule>
<!-- columns:分片字段 -->
<columns>areacode</columns>
<!-- algorithm:分片函数,对应下面的function标签 -->
<algorithm>hash-int</algorithm>
</rule>
</tableRule>

<function name="hash-int" class="io.mycat.route.function.PartitionByFileMap">
<!-- mapFile: 标识配置文件名称-->
<property name="mapFile">partition-hash-int.txt</property>
<!-- type: 0为int型、 非0为String -->
<property name="type">1</property>
<!-- defaultNode: 默认节点:小于 0 表示不设置默认节点,大于等于 0 表示设置默认节点,
设置默认节点如果碰到不识别的枚举值,就让它路由到默认节点,如不设置不识别就报错 -->
<property name="defaultNode">0</property>
</function>
修改partition-hash-int.txt文件
1
2
3
4
#10000=0
#10010=1
110=0
120=1
重启mycat
建表
1
2
3
4
5
6
7
8
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)
)DEFAULT CHARSET=utf8;
插入数据
1
2
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');

由上图可以看出,mycat会将我们插入的数据根据areacode值与partition-hash-int.txt文件中配置参数比对确定存放的节点,110被分发到node-1,120被分发到node-2


2.3、范围分片

​ 根据分片字段,约定好属于哪一个范围

修改schema.xml文件
1
<table name="payment_info" dataNode="dn1,dn2" rule="auto-sharding-long" ></table>
修改rule.xml文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<tableRule name="auto-sharding-long">
<rule>
<!-- columns:分片字段 -->
<columns>order_id</columns>
<!-- algorithm:分片函数 -->
<algorithm>rang-long</algorithm>
</rule>
</tableRule>

<function name="rang-long" class="io.mycat.route.function.AutoPartitionByLong">
<!-- mapFile: 标识配置文件名称 -->
<property name="mapFile">autopartition-long.txt</property>
<!-- defaultNode: 默认节点:小于 0 表示不设置默认节点,大于等于 0 表示设置默认节点,
设置默认节点如果碰到不识别的枚举值,就让它路由到默认节点,如不设置不识别就报错 -->
<property name="defaultNode">0</property>
</function>
修改autopartition-long.txt文件
1
2
3
4
5
6
7
# range start-end ,data node index
# K=1000,M=10000.
#0-500M=0
#500M-1000M=1
#1000M-1500M=2
0-102=0
103-200=1
重启mycat
建表
1
2
3
4
5
6
7
CREATE TABLE payment_info
(
`id` INT AUTO_INCREMENT comment '编号',
`order_id` INT comment '订单编号',
`payment_status` INT comment '支付状态',
PRIMARY KEY(id)
);
插入数据
1
2
3
4
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);
查询结果

由上图可以看出,mycat会将我们插入的数据根据order_id值与autopartition-long.txt文件中配置参数比对确定存放的节点


2.4、范围求模算法

​ 该算法为先进行范围分片,计算出分片组,组内再求模,综合了范围分片和求模分片的优点。

​ 分片组内使用求模可以保证组内的数据分步比较均匀,分片组之间采用范围分片可以兼顾范围分片的特点。事先规定好分片的数量,数据扩容时按分片组扩容,则原有分片组的数据不需要迁移。由于分片组内的数据分步比较均匀,所以分片组内可以避免热点数据问题。

修改schema.xml文件
1
<table name="person" primaryKey="id" dataNode="dn1,dn2,dn3" rule="auto-sharding-rang-mod"></table>
修改rule.xml文件
1
2
3
4
5
6
7
8
9
10
11
<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>
修改partition-range-mod.txt文件
1
2
3
4
5
6
7
8
# range start-end ,data node group size
# 0-200M=5
# 200M1-400M=1
# 400M1-600M=4
# 600M1-800M=4
# 800M1-1000M=6
0-1M=2
1M1-2M=1
重启mycat
建表
1
2
3
4
5
CREATE TABLE `person` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(50) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
插入数据
1
2
3
4
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');
查询结果

由上图可以看出,mycat会将我们插入的数据根据id值与partition-range-mod.txt文件中配置参数比对,确定存放在哪个分片,如果该分片只有一个节点直接放入,分片存在多个节点时,需要对id值进行取模运算确定存放的节点

先匹配范围,再取模


2.5、固定分片hash算法

​ 类似于十进制的求模运算,但是为二进制的操作,取id的二进制低10位,即id的二进制 & 1111111111,结果落在0-1023之间,所以默认最大分片为1024.

​ 此算法的优点在于如果按照十进制取模运算,则在连续插入110时,110会被分到1~10个分片,增大了插入事务的控制难度。而此算法根据二进制则可能会分到连续的分片,降低了插入事务的控制难度。

修改schema.xml文件
1
<table name="user" primaryKey="id" dataNode="dn1,dn2,dn3" rule="rule1"></table>
修改rule.xml文件
1
2
3
4
5
6
7
8
9
10
11
12
13
<tableRule name="rule1">
<rule>
<columns>id</columns>
<algorithm>func1</algorithm>
</rule>
</tableRule>

<function name="func1" class="io.mycat.route.function.PartitionByLong">
<!-- partitionCount为分片个数列表 -->
<property name="partitionCount">2,1</property>
<!-- partitionLength为分片范围列表,分区长度默认最大为1024,即最大支持1024个分区 -->
<property name="partitionLength">256,512</property>
</function>
重启mycat
建表
1
2
3
4
5
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(50) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
插入数据
1
2
3
4
5
6
7
8
9
10
11
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');
insert into user(id,name) values(1025,'zhangsan2');
insert into user(id,name) values(1025,'zhangsan5');
insert into user(id,name) values(1257,'zhangsan6');
insert into user(id,name) values(1281,'zhangsan7');
insert into user(id,name) values(256,'zhangsan8');
insert into user(id,name) values(511,'zhangsan9');
insert into user(id,name) values(512,'zhangsan10');
查询结果

由上图可以看出,mycat会将我们插入的数据根据id值与rule.xml文件中配置参数比对,确定存放在哪个分片,当数值超过1024时会减去1024再进行范围匹配

最大分片数为1024,范围从0-1023


2.6、取模范围算法

​ 取模运算与范围约束的结合主要是为后续的数据迁移做准备,即可以自主决定取模后数据的节点分布。

修改schema.xml文件
1
<table name="user2" primaryKey="id" dataNode="dn1,dn2,dn3" rule="sharding-by-pattern"></table>
修改rule.xml文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<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">
<!-- mapFile:切分规则配置文件 -->
<property name="mapFile">partition-pattern.txt</property>
<!-- patternValue:求模基数 -->
<property name="patternValue">256</property>
<!-- defaultNode:默认节点,小于0表示不设置默认节点,大于等于0表示设置默认节点,如果超出配置的范围,则使用默认节点 -->
<property name="defaultNode">0</property>
</function>
修改partition-pattern.txt文件
1
2
3
0-86=0
87-173=1
174-256=2
重启mycat
建表
1
2
3
4
5
CREATE TABLE `user2` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(50) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
插入数据
1
2
3
4
5
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');
insert into user2(id,name) values(257,'zhangsan4');
insert into user2(id,name) values(343,'zhangsan5');
insert into user2(id,name) values(430,'zhangsan6');
查询结果

由上图可以看出,mycat会将我们插入的数据根据id值与rule.xml文件中配置的取模基数patternValue进行计算,看结果落在哪个范围分片,最终确定存放在哪个节点

先取模,再匹配范围


2.7、字符串hash求模范围算法

​ 与取模范围算法类似,该算法支持数值、符号、字母取模,此方式就是将指定位数的字符的ascll码的和进行取模运算。

修改schema.xml文件
1
<table name="user3" primaryKey="id" dataNode="dn1,dn2,dn3" rule="sharding-by-prefixpattern"></table>
修改rule.xml文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<tableRule name="sharding-by-prefixpattern">
<rule>
<!-- columns标识将要分片的表字段 -->
<columns>name</columns>
<!-- algorithm为分片函数 -->
<algorithm>sharding-by-prefixpattern</algorithm>
</rule>
</tableRule>

<function name="sharding-by-prefixpattern" class="io.mycat.route.function.PartitionByPrefixPattern">
<!-- mapFile:切分规则配置文件 -->
<property name="mapFile">partition-pattern.txt</property>
<!-- patternValue:求模基数 -->
<property name="patternValue">256</property>
<!-- prefixLength:ASCII 截取的位数 -->
<property name="prefixLength">5</property>
</function>
修改partition-pattern.txt文件
1
2
3
4
5
6
7
# ASCII
# 8-57=0-9 阿拉伯数字
# 64、 65-90=@、 A-Z
# 97-122=a-z
0-86=0
87-173=1
174-256=2
重启mycat
建表
1
2
3
4
5
CREATE TABLE `user3` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(50) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
插入数据
1
2
3
4
5
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');
查询结果

由上图可以看出,mycat会将我们插入的数据根据name值的前五位进行截取计算ASCII值,结果值再与rule.xml文件中配置的取模基数patternValue进行取模计算,看取模结果落在哪个范围分片,最终确定存放在哪个节点

先截取换算ASCII值,取模计算,再匹配范围


2.8、应用指定的算法

​ 在运行阶段由应用程序自主决定路由到哪个分片。

修改schema.xml文件
1
<table name="user4" primaryKey="id" dataNode="dn1,dn2,dn3" rule="sharding-by-substring"></table>
修改rule.xml文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<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>
重启mycat
建表
1
2
3
4
5
CREATE TABLE `user4` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(50) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
插入数据
1
2
3
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');
查询结果

由上图可以看出,mycat会将我们插入的数据根据name值从startIndex截取size数量的字符,结果值匹配分片的索引,最终确定存放在哪个节点


2.9、字符串hash解析算法

​ 字符串hash解析分片,其实就是根据配置的hash预算位规则,将截取的字符串进行hash计算后,得到的int数值即为datanode index(分片节点索引,从0开始)。

修改schema.xml文件
1
<table name="user5" primaryKey="id" dataNode="dn1,dn2,dn3" rule="sharding-by-stringhash"></table>
修改rule.xml5文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<tableRule name="sharding-by-stringhash">
<rule>
<!-- columns标识将要分片的表字段 -->
<columns>id</columns>
<!-- algorithm为分片函数 -->
<algorithm>sharding-by-stringhash</algorithm>
</rule>
</tableRule>
<!--
partitionLength:求模基数
partitionCount:分片数量
hashslice: hash预算位,即根据子字符串中 int 值 hash 运算
0 代表 str.length(), -1 代表 str.length()-1,大于0只代表数字自身
/**
* “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">
<!-- partitionLength为分片范围列表,分区长度默认最大为1024,即最大支持1024个分区 -->
<property name="partitionLength">512,256</property>
<!-- partitionCount为分片个数列表 -->
<property name="partitionCount">1,2</property>
<property name="hashSlice">0:6</property>
</function>
重启mycat
建表
1
2
3
4
5
CREATE TABLE `user5` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(50) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
插入数据
1
2
3
4
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());
查询结果

由上图可以看出,mycat会将我们插入的数据中的id值根据hashslice截取合适的字符串进行hash值计算,再从长度是1024的数组中获取对应的节点索引值,将结果存入该节点

最大分片数为1024,范围从0-1023


2.10、按照日期范围分片

​ 按照某个指定的日期进行分片

修改schema.xml文件
1
<table name="login_info" dataNode="dn1,dn2" rule="sharding_by_date" ></table>
修改rule.xml文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<tableRule name="sharding_by_date">
<rule>
<!-- columns:分片字段 -->
<columns>login_date</columns>
<!-- algorithm:分片函数 -->
<algorithm>shardingByDate</algorithm>
</rule>
</tableRule>

<function name="shardingByDate" class="io.mycat.route.function.PartitionByDate">
<!-- sBeginDate :开始日期 -->
<property name="dateFormat">yyyy-MM-dd</property>
<!-- sBeginDate :开始日期 -->
<property name="sBeginDate">2020-06-01</property>
<!-- sEndDate:结束日期,则代表数据达到了这个日期的分片后循环从开始分片插入 -->
<property name="sEndDate">2020-06-04</property>
<!-- sPartionDay :分区天数,即默认从开始日期算起,分隔 2 天一个分区 -->
<property name="sPartionDay">2</property>
</function>
重启mycat
建表
1
2
3
4
5
6
7
CREATE TABLE login_info
(
`id` INT AUTO_INCREMENT comment '编号',
`user_id` INT comment '用户编号',
`login_date` date comment '登录日期',
PRIMARY KEY(id)
);
插入数据
1
2
3
4
5
6
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');
查询结果

按照某个指定的日期进行分片,达到结束日期后循环开始分片插入


2.11、按单月小时分片

​ 此规则是单月内按照小时拆分,最小粒度是小时,可以一天最多24个分片,最少一个分片,一个月完成后下个月开始循环,每个月月尾,需要手工清理数据。

修改schema.xml文件
1
<table name="user6" dataNode="dn1,dn2,dn3" rule="sharding-by-hour"></table>
修改rule.xml文件
1
2
3
4
5
6
7
8
9
10
11
12
<tableRule name="sharding-by-hour">
<rule>
<!-- columns: 拆分字段,字符串类型(yyyymmddHH) -->
<columns>create_time</columns>
<algorithm>sharding-by-hour</algorithm>
</rule>
</tableRule>

<function name="sharding-by-hour" class="io.mycat.route.function.LatestMonthPartion">
<!-- splitOneDay :一天切分的分片数 -->
<property name="splitOneDay">3</property>
</function>
重启mycat
建表
1
2
3
4
5
create table user6(
id int not null,
name varchar(64),
create_time varchar(10)
);
插入数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
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个分片上,但是第二天的数据就无法成功分散了,原因就在于我们的数据分片不够,所以这种方式几乎没有人使用。

ERROR 1064 (HY000): Can’t find a valid data node for specified node index :USER6 -> CREATE_TIME -> 2020060200 -> Index : 3


2.12、日期范围hash分片

​ 思想与范围求模一致,当由于日期在取模会有数据集中问题,所以改成了hash方法。先根据时间hash使得短期内数据分布的更均匀,有点可以避免扩容时的数据迁移,又可以一定程度上避免范围分片的热点问题,要求日期格式尽量精确,不然达不到局部均匀的目的。

修改schema.xml文件
1
<table name="user7" dataNode="dn1,dn2,dn3" rule="rangeDateHash"></table>
修改rule.xml文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<tableRule name="rangeDateHash">
<rule>
<!-- columns: 拆分字段,字符串类型(yyyymmddHH) -->
<columns>create_time</columns>
<!-- algorithm:分片函数 -->
<algorithm>range-date-hash</algorithm>
</rule>
</tableRule>

<function name="range-date-hash" class="io.mycat.route.function.PartitionByRangeDateHash">
<!-- sBeginDate:指定开始的日期,与dateFormat格式一致 -->
<property name="sBeginDate">2020-06-01 00:00:00</property>
<!-- sPartionDay:代表多少天一组 -->
<property name="sPartionDay">3</property>
<!-- dateFormat:指定的日期格式,符合java标准 -->
<property name="dateFormat">yyyy-MM-dd HH:mm:ss</property>
<!-- 一组分片的分片数 -->
<property name="groupPartionSize">1</property>
</function>
重启mycat
建表
1
2
3
4
5
create table user7(
id int not null,
name varchar(64),
create_time varchar(20)
);
插入数据
1
2
3
4
5
6
7
8
9
10
11
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号的数据的时候,没有办法进行数据的插入了,原因就在于没有足够多的数据节点。

insert into user7(id,name,create_time) values(1,’steven’,’2020-06-11 00:00:00’);ERROR 1064 (HY000): Can’t find a valid data node for specified node index :USER7 -> CREATE_TIME -> 2020-06-10 00:00:00 -> Index : 3


2.13、冷热数据分片

​ 根据日期查询冷热数据分布,最近n个月的到实时交易库查询,其他的到其他库中

修改schema.xml文件
1
<table name="user8" dataNode="dn1,dn2,dn3" rule="sharding-by-hotdate" />
修改rule.xml文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<tableRule name="sharding-by-hotdate">
<rule>
<columns>create_time</columns>
<algorithm>sharding-by-hotdate</algorithm>
</rule>
</tableRule>

<function name="sharding-by-hotdate" class="io.mycat.route.function.PartitionByHotDate">
<!-- dataFormat:时间格式化 -->
<property name="dateFormat">yyyy-MM-dd</property>
<!-- sLastDay:热数据的天数 -->
<property name="sLastDay">10</property>
<!-- sPartionDay:冷数据的分片天数(按照天数分片)-->
<property name="sPartionDay">30</property>
</function>
重启mycat
建表
1
CREATE TABLE  user8(create_time timestamp NULL ON UPDATE CURRENT_TIMESTAMP  ,`db_nm` varchar(20) NULL);
插入数据
1
2
3
4
5
6
7
8
9
10
11
INSERT INTO user8 (create_time,db_nm) VALUES ('2021-03-01', database());
INSERT INTO user8 (create_time,db_nm) VALUES ('2021-04-01', database());
INSERT INTO user8 (create_time,db_nm) VALUES ('2021-04-10', database());
INSERT INTO user8 (create_time,db_nm) VALUES ('2021-04-11', database());
INSERT INTO user8 (create_time,db_nm) VALUES ('2021-04-21', database());
INSERT INTO user8 (create_time,db_nm) VALUES ('2021-04-30', database());
INSERT INTO user8 (create_time,db_nm) VALUES ('2021-05-01', database());
INSERT INTO user8 (create_time,db_nm) VALUES ('2021-05-10', database());
INSERT INTO user8 (create_time,db_nm) VALUES ('2021-05-30', database());
INSERT INTO user8 (create_time,db_nm) VALUES ('2021-03-24', database());
INSERT INTO user8 (create_time,db_nm) VALUES ('2021-03-25', database());
查询结果

当前时间之后及前sLastDay天的数据放入第一个节点

当前操作时间前sPartionDay-当前操作时间前sLastDay天内的数据放入第二个节点

其余当如第三个节点


2.14、自然月分片

修改schema.xml文件
1
<table name="user9" dataNode="dn1,dn2,dn3" rule="sharding-by-month" />
修改rule.xml文件
1
2
3
4
5
6
7
8
9
10
11
12
13
<tableRule name="sharding-by-month">
<rule>
<columns>create_time</columns>
<algorithm>sharding-by-month</algorithm>
</rule>
</tableRule>

<function name="sharding-by-month" class="io.mycat.route.function.PartitionByMonth">
<!-- dateFormat : 日期字符串格式 -->
<property name="dateFormat">yyyy-MM-dd</property>
<!-- sBeginDate : 开始日期 -->
<property name="sBeginDate">2021-01-01</property>
</function>
重启mycat
建表
1
CREATE TABLE  user9(id int,name varchar(10),create_time varchar(20));
插入数据
1
2
3
4
5
6
7
insert into user9(id,name,create_time) values(111,'zhangsan','2021-01-01');
insert into user9(id,name,create_time) values(111,'zhangsan','2021-03-01');
insert into user9(id,name,create_time) values(111,'zhangsan','2021-05-01');
insert into user9(id,name,create_time) values(111,'zhangsan','2021-07-01');
insert into user9(id,name,create_time) values(111,'zhangsan','2021-09-01');
insert into user9(id,name,create_time) values(111,'zhangsan','2021-11-01');
insert into user9(id,name,create_time) values(111,'zhangsan','2021-02-01');
查询结果

sBeginDate指定的月份开始,根据配置的节点数进行分片,因为配置了三个节点,所以如图所示,只有前三个月的数据会被会被分配插入,其余的报错ERROR 1064 (HY000): Can’t find a valid data node for specified node index :USER9 -> CREATE_TIME -> 2021-11-01 -> Index : 10 因为无法找到对应有效的节点


2.15、一致性hash分片

​ 实现方式:一致性hash分片,利用一个分片节点对应一个或者多个虚拟hash桶的思想,尽可能减少分片扩展时的数据迁移。将指定的列值进行hash再对2^32进行取模得到hash环的对应位置

​ 优点:有效解决了分布式数据库的扩容问题。

​ 缺点:在横向扩展的时候,需要迁移部分数据;由于虚拟桶倍数与分片节点数都必须是正整数,而且要服从”虚拟桶倍数×分片节点数=设计极限”,因此在横向扩容的过程中,增加分片节点并不是一台一台地加上去的,而是以一种因式分解的方式增加,因此有浪费物理计算力的可能性。

修改schema.xml文件
1
<table name="user10" dataNode="dn1,dn2,dn3" primaryKey="id" rule="sharding-by-murmur" />
修改rule.xml文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<tableRule name="sharding-by-murmur">
<rule>
<columns>id</columns>
<algorithm>murmur</algorithm>
</rule>
</tableRule>

<function name="murmur" class="io.mycat.route.function.PartitionByMurmurHash">
<!-- 默认是 0-->
<property name="seed">0</property>
<!-- 要分片的数据库节点数量,必须指定,否则没法分片-->
<property name="count">2</property>
<!-- 一个实际的数据库节点被映射为这么多虚拟节点,默认是160倍,也就是虚拟节点数是物理节点数的 160 倍-->
<property name="virtualBucketTimes">160</property>
<!--节点的权重,没有指定权重的节点默认是 1。以 properties 文件的格式填写,以从 0 开始到 count-1 的整数值也就是节点索引为 key,以节点权重值为值。所有权重值必须是正整数,否则以 1 代替 -->
<!--<property name="weightMapFile">weightMapFile</property>-->
<!--用于测试时观察各物理节点与虚拟节点的分布情况,如果指定了这个属性,会把虚拟节点的 murmur hash 值与物理节点的映射按行输出到这个文件,没有默认值,如果不指定,就不会输出任何东西 -->
<property name="bucketMapPath">/etc/mycat/bucketMapPath</property>
</function>
重启mycat
建表
1
create table user10(id bigint not null primary key,name varchar(20));
插入数据
1
2
3
4
5
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());
查询结果

根据count指定分片的节点数量,每个节点会被映射virtualBucketTimes倍的虚拟节点,将columns字段计算hash值再对2^32取模得出hash槽位,顺时针寻找该位最近的节点进行存储


三、分片join

​ Join绝对是关系型数据库中最常用的一个特性,然而在分布式环境中,跨分配的join却是最复杂的,最难解决的一个问题。

​ 性能建议:

  1. 尽量避免使用left join或right join,而用inner join
  2. 在使用left join或right join时,on会优先执行,where条件在最后执行,所以再使用过程中,条件尽可能的在on语句中判断,减少where的执行
  3. 少使用子查询,而用join

​ mycat目前版本支持跨分配的join,主要有四种实现方式:全局表ER分片catletT(人工智能)ShareJoin

全局表

​ 在分片的情况下,当业务表因为规模而进行分片之后,业务表与这个字典表的之间关联会变得比较棘手,因此,在mycat中存在一种全局表,他具备以下特性:

  1. 全局表的插入、更新操作会实时的在所有节点上执行,保持各个分片的数据一致性
  2. 全局表的查询操作,只从一个节点获取
  3. 全局表可以跟任何一个表进行join操作

修改schema.xml文件

1
<table name="dict_order_type" dataNode="dn1,dn2" type="global"></table>

重启mycat

建表

1
2
3
4
5
CREATE TABLE dict_order_type(
id INT AUTO_INCREMENT,
order_type VARCHAR(200),
PRIMARY KEY(id)
);

插入数据

1
2
INSERT INTO dict_order_type(id,order_type) VALUES(101,'type1');
INSERT INTO dict_order_type(id,order_type) VALUES(102,'type2');

查询结果

由图可以看出,每个节点都保存了一份数据,而查询只会从一个节点获取,相当于每个节点都冗余了一个全局表,以便节点内的数据join操作


ER分片

​ 在mycat中,我们已经将orders进行了数据分片,但是orders表跟orders_detail发生关联,如果只把orders_detail放到一个分片上,那么跨库的join很麻烦,所以提出了ER关系的表分片。什么意思呢?就是通过关联关系,将子表与父表关联的记录放在同一个数据分片上。

修改schema.xml文件

1
2
<table name ="orders" dataNode="dn1,dn2" rule="mod_rule">
<childTable name="orders_detail" primaryKey="id" joinKey="order_id" parentKey="id" /> </table>

修改rule.xml文件

1
2
3
4
5
6
<tableRule name="mod_rule">
<rule>
<columns>customer_id</columns>
<algorithm>mod-long</algorithm>
</rule>
</tableRule>

重启mycat

建表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
--订单表 
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)
);

插入数据

1
2
3
4
5
6
7
8
9
10
11
12
13
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);

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);

查询结果

关联关系,将子表与父表关联的记录放在同一个数据分片上,以便数据之间的join关联


Share join

​ ShareJoin是一个简单的跨分片join,基于HBT的方式实现。目前支持2个表的join,原理是解析SQL语句,拆分成单表的SQL语句执行,然后把各个节点的数据汇集。

修改schema.xml文件

1
2
<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文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<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文件

1
2
10000=0
10010=1

重启mycat

建表

1
2
3
create table company(id int primary key,name varchar(10)) engine=innodb;

create table customers(id int not null primary key,name varchar(100),company_id int not null,sharding_id int not null);

插入数据

1
2
3
4
5
insert company (id,name) values(1,'mycat');
insert company (id,name) values(2,'ibm');
insert company (id,name) values(3,'hp');

insert into customers(id,name,company_id,sharding_id)values(1,'wang',1,10000),(2,'xue',2,10010),(3,'feng',3,10000);

查询结果

1
2
3
4
5
6
7
-- 可以看到有时可以查出对应的结果,有时则查询不到
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;

四、全局序列号

1、本地文件方式

​ 使用此方式的时候,mycat讲sequence配置到文件中,当使用到sequence中的配置,mycat会更新sequence_conf.properties文件中sequence当前的值。

配置方式:

​ 在 sequence_conf.properties 文件中做如下配置:

1
2
3
4
GLOBAL_SEQ.HISIDS=
GLOBAL_SEQ.MINID=10001
GLOBAL_SEQ.MAXID=20000
GLOBAL_SEQ.CURID=10000

​ 其中 HISIDS 表示使用过的历史分段(一般无特殊需要可不配置), MINID 表示最小 ID 值, MAXID 表示最大
ID 值, CURID 表示当前 ID 值。
​ server.xml 中配置:

1
<system><property name="sequnceHandlerType">0</property></system>

​ 注: sequnceHandlerType 需要配置为 0,表示使用本地文件方式。

​ 案例使用:

1
2
3
4
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文件

1
<system><property name="sequnceHandlerType">1</property></system>

​ 2、修改schema.xml文件

1
2
<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,添加属性值

1
2
3
4
5
6
#sequence stored in datanode
GLOBAL=dn1
COMPANY=dn1
CUSTOMER=dn1
ORDERS=dn1
MYCAT=dn2

​ 4、在dn2上添加mycat_sequence表

1
2
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表中插入初始记录

1
INSERT INTO mycat_sequence(name,current_value,increment) VALUES ('mycat', -99, 100);

​ 6、在dn2上创建函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
--创建函数
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、插入数据表

1
create table test(id int,name varchar(10));

​ 2、查询对应的序列数据表

1
SELECT * FROM mycat_sequence;

​ 3、向表中插入数据,可以多执行几次

1
insert into test(id,name) values(next value for MYCATSEQ_MYCAT,(select database()));

​ 4、查询添加的数据

1
SELECT * FROM test order by id asc;

​ 5、重新启动mycat,重新添加数据,查看结果,重启之后从101开始

1
SELECT * FROM mycat_sequence;

​ 6、重新查询数据表test

1
SELECT * FROM test order by id asc;

​ 大家在使用的时候会发现报错的情况,这个错误的原因不是因为我们的配置,是因为我们的版本问题,简单替换下版本即可。

1
2
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文件

1
<property name="sequnceHandlerType">2</property>

​ 2、修改sequence_time_conf.properties

1
2
WORKID=06 #任意整数
DATAACENTERID=06 #任意整数

​ 3、修改schema.xml文件

1
<table name="test2" dataNode="dn1,dn2,dn3" primaryKey="id" autoIncrement="true" rule="mod-long" />

​ 4、启动mycat,并且创建表进行测试

1
2
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的配合,此处不再展示,有兴趣的同学下去自己演示即可。