MyCat全局ID主键生成策略详解
一、简介
Mycat是非常好用和优秀的数据库分库分表中间件,其官网地址在这里,里面有非常详细的文档。
分库分表的情况下,多个数据库自增主键无法保证自增主键的全局唯一,因此MyCat的全局主键生成策略孕育而生。
由于MyCat是Java写的,强烈建议可以直接下载源码,进行测试ID生成的情况。
这是启动类:MycatStartup,运行main方法就可以了,启动日志可以看出是正常启动的。
其所有的配置文件如下:
修改server.xml,配置Mycat的用户名和密码。
<!-- 用户名为root、密码为123456、schema.xml指定的名称为TESTDB-->
<user name="root" defaultAccount="true">
<property name="password">123456</property>
<property name="schemas">TESTDB</property>
<property name="defaultSchema">TESTDB</property>
</user>
<user name="user">
<property name="password">user</property>
<property name="schemas">TESTDB</property>
<property name="readOnly">true</property>
<property name="defaultSchema">TESTDB</property>
</user>
修改schema.xml文件,配置MyCat连接的数据库和表。
<mycat:schema xmlns:mycat="http://io.mycat/">
<schema name="TESTDB" checkSQLschema="false" sqlMaxLimit="100" >
<table name="order_info" autoIncrement="true" primaryKey="id" dataNode="test1" rule="mod-long" />
</schema>
<dataNode name="test1" dataHost="localhost" database="macaw" />
<dataHost name="localhost" maxCon="1000" minCon="10" balance="0"
writeType="0" dbType="mysql" dbDriver="jdbc" switchType="1" slaveThreshold="100">
<heartbeat>select user()</heartbeat>
<writeHost host="hostM1" url="jdbc:mysql://localhost:3306" user="root" password="root"></writeHost>
</dataHost>
</mycat:schema>
准备order_info表进行测试
CREATE TABLE `order_info` (
`id` varchar(64) NOT NULL COMMENT '订单ID',
`name` varchar(64) NOT NULL DEFAULT '' COMMENT '订单名称',
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT COMMENT='订单表';
二、ID生成方式
- 本地文件方式:使用服务器本地磁盘文件的方式
- 本地时间戳方式:使用时间戳方式
- 数据库方式:使用数据库的方式
- zookeeper:使用zookeeper生成id
三、四种ID生成方式详解
3.1、本地文件方式
修改 conf/server.xml 文件
<property name="sequenceHandlerType">0</property>
其中,该属性有4个取值:
<!--
0:文件方式
1:数据库方式
2:时间戳方式
3:zk
--/>
当为0时,采用文件的方式,在sequence_conf.properties
文件中,我们可以自定义策略,比如我们要操作order_info表,就可以以ORDER_INFO为前缀配置最大和最小id:
ORDER_INFO.HISIDS=
ORDER_INFO.MINID=1000
ORDER_INFO.MAXID=200000000
ORDER_INFO.CURID=1250
在使用插入的时候需要注意,id列的写法为next value for MYCATSEQ_前缀
,如:
INSERT INTO order_info(id,name) values('next value for MYCATSEQ_ORDER_INFO','文件方式生成的ID');
SELECT * FROM order_info;
优点:本地加载,读取速度较快,配置简单
缺点:mycat重新发布时,seq文件需要替换,集群部署无法用此方式,路由到不同的mycat上无法保证id唯一,使mycat变成了有状态的中间件
3.2、本地时间戳方式
修改server.xml 文件
<property name="sequenceHandlerType">2</property>
修改sequence_time_conf.properties文件
# 0-31为整数,每一个mycat节点的这两个配置值都不一样
WORKID=01
DATAACENTERID=01
INSERT INTO order_info(name) values('时间戳方式生成的ID');
SELECT * FROM order_info;
* 本地时间戳计算方式:
ID= 64 位二进制 (42(毫秒)+5(机器 ID)+5(业务编码)+12(重复累加) 长度18位,因此表主键字段长度必须大于等于18位
优点:不存在mycat重新发布影响seq的问题,
缺点:字段长度是18位,比较占空间
3、数据库方式
修改server.xml 文件
<property name="sequenceHandlerType">1</property>
在mycat的dbseq.sql
文件中有如下建表和函数语句用于基于数据库生成id:
DROP TABLE IF EXISTS MYCAT_SEQUENCE;
CREATE TABLE MYCAT_SEQUENCE ( name VARCHAR(64) NOT NULL, current_value BIGINT(20) NOT NULL, increment INT NOT NULL DEFAULT 1, PRIMARY KEY (name) ) ENGINE=InnoDB;
-- ----------------------------
-- Function structure for `mycat_seq_currval`
-- ----------------------------
DROP FUNCTION IF EXISTS `mycat_seq_currval`;
DELIMITER ;;
CREATE FUNCTION `mycat_seq_currval`(seq_name VARCHAR(64)) RETURNS varchar(64) CHARSET latin1
DETERMINISTIC
BEGIN
DECLARE retval VARCHAR(64);
SET retval="-1,0";
SELECT concat(CAST(current_value AS CHAR),",",CAST(increment AS CHAR) ) INTO retval FROM MYCAT_SEQUENCE WHERE name = seq_name;
RETURN retval ;
END
;;
DELIMITER ;
-- ----------------------------
-- Function structure for `mycat_seq_nextval`
-- ----------------------------
DROP FUNCTION IF EXISTS `mycat_seq_nextval`;
DELIMITER ;;
CREATE FUNCTION `mycat_seq_nextval`(seq_name VARCHAR(64)) RETURNS varchar(64) CHARSET latin1
DETERMINISTIC
BEGIN
DECLARE retval VARCHAR(64);
DECLARE val BIGINT;
DECLARE inc INT;
DECLARE seq_lock INT;
set val = -1;
set inc = 0;
SET seq_lock = -1;
SELECT GET_LOCK(seq_name, 15) into seq_lock;
if seq_lock = 1 then
SELECT current_value + increment, increment INTO val, inc FROM MYCAT_SEQUENCE WHERE name = seq_name for update;
if val != -1 then
UPDATE MYCAT_SEQUENCE SET current_value = val WHERE name = seq_name;
end if;
SELECT RELEASE_LOCK(seq_name) into seq_lock;
end if;
SELECT concat(CAST((val - inc + 1) as CHAR),",",CAST(inc as CHAR)) INTO retval;
RETURN retval;
END
;;
DELIMITER ;
-- ----------------------------
-- Function structure for `mycat_seq_setvals`
-- ----------------------------
DROP FUNCTION IF EXISTS `mycat_seq_nextvals`;
DELIMITER ;;
CREATE FUNCTION `mycat_seq_nextvals`(seq_name VARCHAR(64), count INT) RETURNS VARCHAR(64) CHARSET latin1
DETERMINISTIC
BEGIN
DECLARE retval VARCHAR(64);
DECLARE val BIGINT;
DECLARE seq_lock INT;
SET val = -1;
SET seq_lock = -1;
SELECT GET_LOCK(seq_name, 15) into seq_lock;
if seq_lock = 1 then
SELECT current_value + count INTO val FROM MYCAT_SEQUENCE WHERE name = seq_name for update;
IF val != -1 THEN
UPDATE MYCAT_SEQUENCE SET current_value = val WHERE name = seq_name;
END IF;
SELECT RELEASE_LOCK(seq_name) into seq_lock;
end if;
SELECT CONCAT(CAST((val - count + 1) as CHAR), ",", CAST(val as CHAR)) INTO retval;
RETURN retval;
END
;;
DELIMITER ;
-- ----------------------------
-- Function structure for `mycat_seq_setval`
-- ----------------------------
DROP FUNCTION IF EXISTS `mycat_seq_setval`;
DELIMITER ;;
CREATE FUNCTION `mycat_seq_setval`(seq_name VARCHAR(64), value BIGINT) RETURNS varchar(64) CHARSET latin1
DETERMINISTIC
BEGIN
DECLARE retval VARCHAR(64);
DECLARE inc INT;
SET inc = 0;
SELECT increment INTO inc FROM MYCAT_SEQUENCE WHERE name = seq_name;
UPDATE MYCAT_SEQUENCE SET current_value = value WHERE name = seq_name;
SELECT concat(CAST(value as CHAR),",",CAST(inc as CHAR)) INTO retval;
RETURN retval;
END
;;
DELIMITER ;
INSERT INTO MYCAT_SEQUENCE VALUES ('GLOBAL', 1, 1);
MYCAT_SEQUENCE 的三个字段:
- name sequence名称
- current_value 当前value
- increment 增长步长 可理解为mycat在数据库中一次读取多少个sequence. 当这些用完后, 下次再从数据库中读取.
插入一条数据,用于order_info表id生成:
INSERT INTO `MYCAT_SEQUENCE`(`name`, `current_value`, `increment`) VALUES ('ORDER_INFO', 23650, 100);
接着需要在sequence_db_conf.properties中进行配置:指定某个sequence哪个节点上,与dataNode的name对应。
<dataNode name="test1" dataHost="localhost" database="macaw" />
GLOBAL=test1
ORDER_INFO=test1
执行sql进行测试
INSERT INTO order_info(name) values('数据库方式生成的ID');
SELECT * FROM order_info;
* 注意 :将mycat_sequence表也放出来,且注意大小写(数据库默认区分大小写)
优点:重新部署mycat不受影响,节点如果是主从切换后,数据id可能会有异常(重复)
缺点:这种策略,只能在当前库中使用, 但是如果进行分库,那么在多个库中,就一定会出现id相同的问题。
3.4、ZK方式
修改server.xml 文件
<property name="sequnceHandlerType">3</property>
修改myid.properties
#使用zk管理mycat
loadZk=true
#zk服务器的地址和端口
zkURL=127.0.0.1:2181
#mycat集群的ID
clusterId=1
#本mycat的ID
myid=mycat_fz_01
#集群大小
clusterSize=1
#集群内mycat的ID
clusterNodes=mycat_fz_01
#server booster ; booster install on db same server,will reset all minCon to 2
type=server
boosterDataHosts=dataHost1
修改sequence_distributed_conf.properties
INSTANCEID=ZK #代表使用zk
CLUSTERID=1 #与myid.properties中的CLUSTERID设置的值相同
修改schema.xml
schema的table 增加属性 autoIncrement="true"和 primaryKey="id"
测试之前请启动zookeeper!
INSERT INTO order_info(name) values('zk方式生成的ID');
SELECT * FROM order_info;
优点:无悲观锁,无强竞争,吞吐量更高
缺点:对zookeeper集群的要求增加