Mycat全局序列
在实现分库分表的情况下,数据库自增主键已无法保证自增主键的全局唯一。为此,Mycat 提供了全局sequence(序列号),并且提供了包含本地配置和数据库配置等多种实现方式,常见的有以下三种:本地文件方式、数据库方式和本地时间戳方式。
注意:Mycat全局序列是在Mycat水平分表基础上搭建起来的,关于Mycat的水平分表,可戳:Mycat实现水平分表及读写分离
本地文件方式
原理:此方式Mycat将sequence(序列号)配置到sequence_conf.properties文件中,当使用到配置的sequence后,Mycat会自动更新sequence_conf.properties文件中sequence的CURID值。
配置Mycat安装目录/conf/sequence_conf.properties
#default global sequence
#使用过的历史分段(一般无特殊需要可不配置)
GLOBAL.HISIDS=
#初始序列号
GLOBAL.MINID=1200
#结束序列号
GLOBAL.MAXID=1900
#当前序列号(已使用)
GLOBAL.CURID=1199
配置Mycat安装目录/conf/server.xml,sequnceHandlerType改为0,表示使用本地文件方式启用全局序列号,默认这项是被注释的,取消注释修改数值即可。
<property name="sequnceHandlerType">0</property>
启动Mycat,插入数据
INSERT INTO `t_student` (id,s_name) VALUES ("next value for MYCATSEQ_GLOBAL", 'atuo');
插一个bug:如果next value for MYCATSEQ_GLOBAL不加引号,则会报错:Lost connection to MySQL server during query,但是有些文章不加引号,是不会报错的,这里有点疑惑,还望有人解惑。
select 一下数据,看配置的全局序列号是否插入到数据当中
select * from t_student;
插入成功
此时我们可以打开Mycat安装目录/conf/sequence_conf.properties文件,看下GLOBAL.CURID这一项,已被自动更新为1200。
数据库方式
原理:在数据库中建立一张表(表名MYCAT_SEQUENCE),存放 sequence 名称(name),sequence 当前值(current_value),步长(increment,每次读取多少个 sequence)等信息;
Sequence 获取步骤:
-
当初次使用该 sequence 时,根据传入的 sequence 名称,从数据库这张表中读取 current_value,和increment (得到一个sequence号段)到 MyCat 中,并将数据库中的这张表的 current_value 设置为原 current_value 值+increment 值;
-
Mycat 将读取到 current_value+1 作为每次插入数据要使用的 sequence 值,当使用 increment 次后,执行上面步骤相同的操作;
-
若Mycat某次读取的sequence号段还有一串值没有用完,系统就停掉了,则这次读取的 sequence 号段的剩余值不会再使用,没了就没了,只需保证每次插入数据的sequence值是唯一的;
在Mycat的其中一个分片节点上dn5(对应MySQL的db5数据库下)上创建该MYCAT_SEQUENCE表
use db5
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;
为MYCAT_SEQUENCE表插入数据(序列名 起始值 步长)
INSERT INTO MYCAT_SEQUENCE(name,current_value,increment) VALUES ('USERS', 300,100);
创建相应的函数一:传入序列名,获取当前sequence的值
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设定当前值(指定具体value)
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设定当前值(当前值=原当前值+步长)
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 ;
配置Mycat的/conf/sequence_db_conf.properties(指明所用的序列名,以及MYCAT_SEQUENCE表在哪个分片节点上)
USERS=dn5
配置Mycat的/conf/server.xml,表示所用的全局序列方式为数据库方式
<property name="sequnceHandlerType">1</property>
重启Mycat,登录Mycat
插入数据
INSERT INTO `t_student` (id,s_name) VALUES ("next value for MYCATSEQ_USERS", 'atuo');
INSERT INTO `t_student` (id,s_name) VALUES ("next value for MYCATSEQ_USERS", 'hjq');
此时在Mycat里select一下数据,可以看到插入的数据记录已经自动获取主键值
select * from t_student;
本地时间戳方式
创建一个逻辑表用于测试实现主键按时间戳自增,指明主键字段,并开启自动增长
<table name="time_test" dataNode="dn1,dn3" primaryKey="id" autoIncrement="true" />
配置Mycat的conf/server.xml,指明所用的全局序列方式为本地时间戳方式
<property name="sequnceHandlerType">2</property>
配置/conf/sequence_time_conf.properties,配置所在Mycat节点的ID,因为这里没有做Mycat集群,就使用默认的01好了,但要注意,如果配置了Mycat集群,每个Mycat节点下配置的 WORKID,DATAACENTERID都要不同,组成唯一标识,总共支持32*32=1024种组合 (因为WORDID可配置范围1-31,DATAACENTERTD可配置范围1-31) 。
重启下Mycat,连接Mycat,选择相应的逻辑库,并创建配置的相应逻辑表,这里要注意,创建的表的主键id类型为bigint,不再是int,因为使用本地时间戳方式生成的id字段会过长,超出int类型的长度范围。
CREATE TABLE `time_test` (
`id` bigint NOT NULL,
`s_name` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
插入数据
INSERT INTO `time_test` (s_name) VALUES ('time1');
INSERT INTO `time_test` (s_name) VALUES ('time2');
插说个bug:当插入数据时如果报出错误:Table ‘db3.time_test’ doesn’t exist,这是因为对应的物理库dn1,dn3没有存在time_test这张表导致。只需要到逻辑表指定的相应的物理库执行以下上面的建表语句,创建一遍time_test这张表即可。但纳闷的是,当我一开始在Mycat创建time_test这张表的时候,相应的dn1、dn3却是创建了TIME_TEST表(表名变大写),而不是time_test表。如果有人知道原因,还望告知。
select 一下数据,看是否主键已经实现本地时间戳自增
select * from time_test;
主键已经自动按本地时间戳自增(这里由于我没有设置time_test逻辑表分片,所以它插入数据时候往每一个分片节点都插入了相同的数据,查询时候也自然返回同样的两份数据)
总结
上面简单示范了一下实现Mycat全局序列的三种方式,这里来总结一下这三种方式的优缺点
- 本地文件方式:优点是序列号由本地加载,获取速度快;缺点是,每一次Mycat重启,其序列的当前值都会回到最初配置的值,会导致主键(id)重复。
- 数据库方式:优点是每一次Mycat重启,序列号的当前值都不会回到最初配置的值,弥补了本地文件方式的缺点;缺点是万一存序列相关信息的那张表所在的数据库挂了(MYCAT_SEQUENCE),这就涉及到单点故障问题了。
- 本地时间戳方式:优点是不存在以上两种方式的缺点;缺点是根据时间戳自动生成的主键数据类型长度过大,会造成存储浪费。