MyCat单库分表详解
前言
MyCat官网:http://mycat.io/
安装包库:http://dl.mycat.io/
Linux版本:CentOS7
JDK版本:1.8.0,因为MyCat使用Java写的,所以需要安装JDK
Docker版本:Docker version 1.13.1, build b2f74b2/1.13.1
本文所使用的MyCat版本:mycat1.6.7.1
主流分表中间件
中间件 | 优势 | 劣势 | 推荐星 |
---|---|---|---|
Cobar | 阿里最早起的数据库中间件,现无人维护 | 无人维护导致使用成本高,难度较大 | 2 |
MyCat | 基于Cobar 进行二次开发,采用代理模式来进行数据库分库分表,也是目前国内较为受欢迎的中间件,低侵入性,高性能 | 对于复杂的SQL不支持,只支持基本函数,不支持复杂join等,具体可参考官方文档。http://www.mycat.io/document/mycat-definitive-guide.pdf | 5 |
ShardingJDBC | Sharding-Sphere 1.X,是由当当的应用框架dd-frame中的dd-rdb模块演进而来,最终抽离并开源,是完全由当当孵化的开源产品,采用Apache 2.0协议,著作版权归 http://dangdang.com 所有。侵入性较高,可进行灵活的配置 | 使用难度系数大,如果是分布式系统需在每个服务中进行配置,配置难度高 | 3 |
ShardingProxy | Sharding-Sphere 2.X,原理与MyCat 一样,进过使用测试,在单库分表能力上比MyCat优秀,社区也更活跃。 | 对一些MySQL类型不支持,如bit(可用int替代);存在乱码问题,但通过源码编译使用,也可得到解决,一般公司使用都会使用源码编译方式,便于修改问题。详细请参考:https://shardingsphere.apache.org/document/current/cn/manual/sharding-proxy/,本文不做介绍。 | 6 |
Sharding-Sidecar | Sharding-Sphere 3.X,目前仍然在规划中,据官方介绍,定位为Kubernetes或Mesos的云原生数据库代理,以DaemonSet的形式代理所有对数据库的访问,功能十分强大。 | 全新的数据库中间件概念,面临着诸多挑战,需要大量的完善维护,不稳定。应用面不适合小打小闹的项目 | 6 |
OneProxy | 前支付宝数据库团队领导楼总开发,C语言开发,支持连接池、故障切换、读写分离、分库分表、SQL防火墙、SQL监控等多项实用功能,主打高性能和稳定性。 | 是商业化中间件,收费。 | 5 |
单库分表需求说明
项目初期,用户量小于500w,具备下单、撮合、频繁Job查询类业务,单表性能无法保证系统可用性。因为项目已上线,如果进行分库分表,对代码改动大,且就目前用户量而言无需进行分库,分表即可保证查询速度。
写在前面的关于数据分库分表的一些建议
- 达到一定数量级才拆分(800万);
- 不到800万但跟大表(超800万的表)有关联查询的表也要拆分,在此称为大表关联表;
- 大表关联表如何拆:小于100万的使用全局表;大于100万小于800万跟大表使用同样的拆分策略;无法跟大表使用相同规则的,可以考虑从java代码上分步骤查询,不用关联查询,或者破例使用全局表;
- 【真实案例】破例的全局表:如item_sku表250万,跟大表关联了,又无法跟大表使用相同拆分策略,也做成了全局表。破例的全局表必须满足的条件:没有太激烈的并发update,如多线程同时update同一条id=1的记录。虽有多线程update,但不是操作同一行记录的不在此列。多线程update全局表的同一行记录会死锁。批量insert没问题;
- 拆分字段是不可修改的;
- 拆分字段只能是一个字段,如果想按照两个字段拆分,必须新建一个冗余字段,冗余字段的值使用两个字段的值拼接而成(如大区+年月拼成zone_yyyymm字段);
- 拆分算法的选择和合理性评判:按照选定的算法拆分后每个库中单表不得超过800万;
- 能不拆的就尽量不拆。如果某个表不跟其他表关联查询,数据量又少,直接不拆分,使用单库即可。
更多建议请参考:http://www.zhongruitech.com/65556184.html,http://www.unjeep.com/article/18497.html (备用)
MyCat 单库分表支持说明
MyCat自1.6版本后支持单库分表,网传只有1.6版本支持单库分表不可信,博主亲测MyCat1.6.5、1.6.7.1、1.7-BETA、2.0-dev都支持单库分表,但又有略微区别,其中推荐使用1.6.7.1版本,是目前的稳定版本。
MyCat Linux安装之Tar包解压
Yum安装JDK8(如果你安装的JDK非1.8建议升级到1.8,之前版本无效卸载,改变环境变量配置即可)
1、查看1.8版本安装包列表
yum search java-1.8
2、yum安装指定版本JDK
yum -y install java-1.8.0-openjdk-devel.x86_64
安装完成 路径一般在 /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.151-1.b12.el7_4.x86_64
3、查看是否安装成功
[root@localhost jvm]# java -version
openjdk version "1.8.0_151"
OpenJDK Runtime Environment (build 1.8.0_151-b12)
OpenJDK 64-Bit Server VM (build 25.151-b12, mixed mode)
[root@localhost jvm]# javac -version
javac 1.8.0_151
4、修改环境变量
配置环境变量 vim /etc/profile, 在文件末尾加上:
export JAVA_HOME=/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.151-1.b12.el7_4.x86_64
export PATH=$JAVA_HOME/bin:$PATH
export CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
5、执行 . /etc/profile 使用配置生效
安装MyCat1.6.7.1
在官方我们可以看到MyCat详细的版本更新迭代日志,以及个版本下载链接。
通过wget拉取tar包,wget命令不存在可通过 yum install wget进行在线安装其模块!
wget http://dl.mycat.io/1.6.7.1/Mycat-server-1.6.7.1-release-20190213150257-linux.tar.gz
然后解压安装包
tar -zxvf Mycat-server-1.6.7.1-release-20190213150257-linux.tar.gz
MyCat 安装之制作Docker 镜像
安装Docker环境,此处不做详细介绍,参考:https://www.runoob.com/docker/docker-tutorial.html
1、任意路径,创建文件夹mycat
mkdir /home/mycat
2、拉取mycat压缩包
wget http://dl.mycat.io/1.6.7.1/Mycat-server-1.6.7.1-release-20190213150257-linux.tar.gz
3、重命名
mv Mycat-server-1.6.7.1-release-20190213150257-linux.tar.gz mycat1.6.7.1.tar
4、编写Dockerfile
vim Dockerfile
Dockerfile内容:
FROM openjdk:8
ADD mycat1.6.7.1.tar.gz /usr/local/
VOLUME ["/usr/local/mycat/conf","/usr/local/mycat/logs"]
ENV MYCAT_HOME=/home/mycat
EXPOSE 8066 8066
RUN chmod -R 777 /usr/local/mycat/bin
CMD ["./usr/local/mycat/bin/mycat", "console"]
说明:
FROM:指定基础环境镜像为openjdk:8,
ADD:将mycat解压到/usr/local目录中,得到 /usr/local
VOLUME:配置暴露出的映射地址,之后可将这两个文件夹在启动时直接映射宿主机的文件夹
ENV:设置MYCAT_HOME
EXPOSE:暴露出MyCat的所需端口
RUN:执行运行时特殊指令,此处只改变了文件权限
CMD:以前台进程的方式启动MyCat服务
5、打包镜像【注意最后的那个点】
docker build -t mycat:1.6.7.1 .
6、启动容器
docker run -p 8066:8066 -d --name mycat_node1 --net host -v /home/mycat/conf/:/usr/local/mycat/conf -v /home/mycat/logs/:/usr/local/mycat/logs/ mycat:1.6.7.1
7、查看是否启动成功
cat wrapper.log
8、提供一下备用命令
netstat -ntpl -----查看出站端口
MyCat配置文件说明
MyCat核心配置文件server.xml、schema.xml、rule.xml,两大日志文件wrapper.log、mycat.log。
文件 | 作用说明 |
---|---|
schema.xml | 管理MyCat的逻辑库、表、分片规则的配置文件 |
rule.xml | 配置MyCat分片路由策略 |
server.xml | 配置中定义用户以及系统相关变量,如端口等。 |
wrapper.log | logs目录下wrapper.log文件记录MyCat启动日志文件,移动成功、异常信息。 |
mycat.log | logs目录下mycat.log文件记录执行的SQL日志信息,运行过程中SQL错误信息、分表后的SQL语句日志。 |
partition-hash-int.txt | 配置枚举分表的映射关系(分表列值&表名索引)的文件,参考下文理解。 |
sequence_db_conf.properties | 配置唯一性自增ID数据库实现方式的序列映射关系(序列名&数据节点名)的文件,参考下文理解。 |
三个配置文件的详细说明请参考:http://www.cnblogs.com/wuzhenzhao/p/10188973.html 一文。或者官方文档:http://www.mycat.io/document/mycat-definitive-guide.pdf
单库分表之取模分片算法
数据库表结构
分表为:t_user_00、t_user_01、t_user_02,非分表:t_stu、其余三表请无视。
DROP TABLE IF EXISTS `t_user_00`;
CREATE TABLE `t_user_00` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` bigint(20) NULL DEFAULT NULL,
`name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`age` int(11) NOT NULL,
`create_time` datetime(0) NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 35 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;
SET FOREIGN_KEY_CHECKS = 1;
server.xml文件:
<mycat:server xmlns:mycat="http://io.mycat/">
<system>
<!-- 配置MyCat ID自增方式,0 为本地文件方式,1 为数据库方式,2 为时间戳序列方式,3 为分布式 ZK ID 生成器,4 为 zk 递增 id 生成。 从 1.6 增加 两种 ZK 的全局 ID 生成算法。详细请参考:http://www.mycat.io/document/mycat-definitive-guide.pdf -->
<property name="sequnceHandlerType">1</property>
</system>
<!-- 配置逻辑数据库的读写都可用的用户,如果你是使用的1.7以后版本,请去掉defaultAccount属性 -->
<user name="root" defaultAccount="true">
<property name="password">123456</property>
<!-- 指定逻辑数据库-->
<property name="schemas">mycat_testdb</property>
</user>
<!-- 只读用户 -->
<user name="user">
<property name="password">user</property>
<property name="schemas">mycat_testdb</property>
<!-- 是否只读,用于实现读写分离 -->
<property name="readOnly">true</property>
</user>
</mycat:server>
rule.xml文件:
<mycat:rule xmlns:mycat="http://io.mycat/">
<!-- 定义一个表路由规则 -->
<tableRule name="rule1">
<rule>
<!-- id为真实分表字段,这里为路由字段 -->
<columns>id</columns>
<!-- 指定路由则规则所使用的算法 -->
<algorithm>rang-mod</algorithm>
</rule>
</tableRule>
<!-- 定义取模算法,name表示名称,class表示所使用的算法类 -->
<function name="rang-mod" class="io.mycat.route.function.PartitionByMod">
<!-- count表示分表的个数 -->
<property name="count">3</property>
</function>
</mycat:rule>
schema.xml文件
<?xml version="1.0"?>
<!DOCTYPE mycat:schema SYSTEM "schema.dtd">
<mycat:schema xmlns:mycat="http://io.mycat/">
<!-- name:是mycat的逻辑库名称,必须保证和server.xml文件中配置的schemas一致,链接需要用的
checkSQLschema:是否检测SQL语句中的MyCat特殊语义,比如如果采用数据库方式实现自增ID,则SQL语句中ID字段的值需改成next value for MYCATSEQ_TEST,如果checkSQLschema为false则不会检测,按字段串处理,开启则会检测为MyCat语义进行ID生成。
-->
<schema name="mycat_testdb" checkSQLschema="false" sqlMaxLimit="100" dataNode="dn1">
<!-- name:逻辑表名
primaryKey:真实主键
autoIncrement:是否开启主键自增
dataNode:指定数据节点(绑定真实数据库)
subTables:指定所有真实分表,t_user_0$0-2为缩写,最终会被解析成t_user_00,t_user_01,t_user_02
role:指定路由策略
-->
<!--<table name="t_user" primaryKey="id" autoIncrement="true" dataNode="dn1" subTables="t_user_00,t_user_01,t_user_02" rule="rule1"/> -->
<table name="t_user" primaryKey="id" autoIncrement="true" dataNode="dn1" subTables="t_user_0$0-2" rule="rule1"/>
<!-- 以下为不进行分表的表配置 -->
<table name="t_no_database" dataNode="dn1" />
<table name="t_order_201708" dataNode="dn1" />
<table name="t_order_201709" dataNode="dn1" />
<table name="t_stu" dataNode="dn1" />
</schema>
<!-- database 是MySQL数据库的库名 -->
<dataNode name="dn1" dataHost="localhost1" database="sharding_0" />
<!--
dataNode节点中各属性说明:
name:指定逻辑数据节点名称;
dataHost:指定逻辑数据节点物理主机节点名称;
database:指定物理主机节点上。如果一个节点上有多个库,可使用表达式db$0-99, 表示指定0-99这100个数据库;
dataHost 节点中各属性说明:
name:物理主机节点名称;
maxCon:指定物理主机服务最大支持1000个连接;
minCon:指定物理主机服务最小保持10个连接;
writeType:指定写入类型;
0,只在writeHost节点写入;
1,在所有节点都写入。慎重开启,多节点写入顺序为默认写入根据配置顺序,第一个挂掉切换另一个;
dbType:指定数据库类型;
dbDriver:指定数据库驱动;
balance:指定物理主机服务的负载模式。
0,不开启读写分离机制;
1,全部的readHost与stand by writeHost参与select语句的负载均衡,简单的说,当双主双从模式(M1->S1,M2->S2,并且M1与 M2互为主备),正常情况下,M2,S1,S2都参与select语句的负载均衡;
2,所有的readHost与writeHost都参与select语句的负载均衡,也就是说,当系统的写操作压力不大的情况下,所有主机都可以承担负载均衡;
-->
<dataHost name="localhost1" maxCon="1000" minCon="10" balance="3" writeType="0" dbType="mysql" dbDriver="native" switchType="1" slaveThreshold="100">
<heartbeat>select user()</heartbeat>
<!-- 可以配置多个主从 -->
<writeHost host="hostM1" url="192.168.1.186:3306" user="root" password="jxmQblOefYV839mg">
<!-- 可以配置多个从库 -->
<readHost host="hostS2" url="192.168.1.186:3306" user="root" password="jxmQblOefYV839mg" />
</writeHost>
</dataHost>
</mycat:schema>
关于1.7+版本defaultAccount属性报错(Attribute “defaultAccount” must be declared for element type “user”.):
tail -1000f logs/wrapper.log
正确启动:
测试取模分片算法
连接MyCat,,分别新增三条数据:
INSERT INTO t_user(`id`, `user_id`, `name`, `age`, `create_time`) VALUES (1, 10001, '张三', 21, '2019-04-16 17:31:17');
INSERT INTO t_user(`id`, `user_id`, `name`, `age`, `create_time`) VALUES (2, 10002, '李四', 22, '2019-04-17 17:32:27');
INSERT INTO t_user(`id`, `user_id`, `name`, `age`, `create_time`) VALUES (3, 10003, '王五', 23, '2019-04-18 17:34:47');
结果发现,名称为“张三”、“李四”、“王五”的数据分别在t_user_01、t_user_02、t_user_00,按照取模算法,1%3=1,2%3=2,3%3=0,所以没问题。再进行查询测试,查询所有即可,如果能查询到三条语句则没问题。
单库分表之枚举分片算法【本文核心】
修改t_user_01、t_user_02、t_user_00表结构,新增city(城市)字段。server.xml文件不变。
DROP TABLE IF EXISTS `t_user_00`;
CREATE TABLE `t_user_00` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` bigint(20) NULL DEFAULT NULL,
`name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`age` int(11) NOT NULL,
`create_time` datetime(0) NULL DEFAULT NULL,
`city` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 35 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;
SET FOREIGN_KEY_CHECKS = 1;
rule.xml文件【注意所有tableRule必须在function 之前 】:
<mycat:rule xmlns:mycat="http://io.mycat/">
<!-- 定义一个表路由规则 -->
<tableRule name="rule1">
<rule>
<!-- name为真实分表字段,这里为路由字段 -->
<columns>id</columns>
<!-- 指定路由则规则所使用的算法 -->
<algorithm>rang-mod</algorithm>
</rule>
</tableRule>
<!-- 枚举规则 -->
<tableRule name="rule2">
<rule>
<columns>city</columns>
<algorithm>hash-int</algorithm>
</rule>
</tableRule>
<!-- 定义取模算法,name表示名称,class表示所使用的算法类 -->
<function name="rang-mod" class="io.mycat.route.function.PartitionByMod">
<!-- count表示分表的个数 -->
<property name="count">3</property>
</function>
<!-- 定义枚举算法,
mapFile:指定枚举映射文件
type:字段属性类型,默认值为0,0表示Integer,非零表示String
defaultNode:指定默认节点,如果city值没有在partition-hash-int.txt中配置枚举映射,则会被映射到默认的节点上,
其值从0开始,对应了schema.xml文件<table>的subTables属性值
-->
<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">1</property>
</function>
</mycat:rule>
schema.xml文件:
<mycat:schema xmlns:mycat="http://io.mycat/">
<!-- mycat_testdb 是mycat的逻辑库名称,必须保证和server.xml文件中配置的schemas一致,链接需要用的 -->
<schema name="mycat_testdb" checkSQLschema="false" sqlMaxLimit="100" dataNode="dn1">
<!-- name:逻辑表名
primaryKey:真实主键
autoIncrement:是否开启主键自增
dataNode:指定数据节点(绑定真实数据库)
subTables:指定所有真实分表,t_user_0$0-2为缩写,最终会被解析成t_user_00,t_user_01,t_user_02
role:指定路由策略
-->
<table name="t_user" primaryKey="id" autoIncrement="true" dataNode="dn1" subTables="t_user_00,t_user_01,t_user_02" rule="rule2"/>
<!--<table name="t_user" primaryKey="id" autoIncrement="true" dataNode="dn1" subTables="t_user_0$0-2" rule="rule2"/> -->
<!-- 以下为不进行分表的表配置 -->
<table name="t_no_database" dataNode="dn1" />
<table name="t_order_201708" dataNode="dn1" />
<table name="t_order_201709" dataNode="dn1" />
<table name="t_stu" dataNode="dn1" />
</schema>
<!-- database 是MySQL数据库的库名 -->
<dataNode name="dn1" dataHost="localhost1" database="sharding_0" />
<dataHost name="localhost1" maxCon="1000" minCon="10" balance="3" writeType="0" dbType="mysql" dbDriver="native" switchType="1" slaveThreshold="100">
<heartbeat>select user()</heartbeat>
<!-- 可以配置多个主从 -->
<writeHost host="hostM1" url="192.168.1.186:3306" user="root" password="jxmQblOefYV839mg">
<!-- 可以配置多个从库 -->
<readHost host="hostS2" url="192.168.1.186:3306" user="root" password="jxmQblOefYV839mg" />
</writeHost>
</dataHost>
</mycat:schema>
partition-hash-int.txt 配置
其中岳阳、深圳等为分表列值(应当包括所有可取值),0为MyCat subTables属性解析后得到的表名数组的索引值。岳阳映射0,0映射到subTables[0],最终将映射到表名t_user_00,所以必须保证无误!
岳阳=0
深圳=1
其他=2
分片枚举算法测试与原理分析
INSERT INTO t_user(`id`, `user_id`, `name`, `age`, `create_time`,`city`) VALUES (1, 10001, '张三', 21, '2019-04-16 17:31:17','岳阳');
INSERT INTO t_user(`id`, `user_id`, `name`, `age`, `create_time`,`city`) VALUES (2, 10002, '李四', 22, '2019-04-17 17:32:27','深圳');
INSERT INTO t_user(`id`, `user_id`, `name`, `age`, `create_time`,`city`) VALUES (3, 10003, '王五', 23, '2019-04-18 17:34:47','beijing');
我们新增三条数据,然后通过观察查询所有和根据city条件查询时,MyCat打印的日志分析【分片枚举算法】怎么进行的SQL优化,以及如何定位的分表:
select * from t_user;
select * from t_user where city = '岳阳';
查询mycat.log日志:
tail -1000f mycat.log
通过对比两个SQL语句执行时日志,不难看出,当查询不带city条件时,会进行全分表查询,显而易见,MyCat会根据我们设定的city字段进行分表查询,那它又是如何定位分表的呢?下面我们来看一下枚举算法路由的过程:
select * from t_user where city = '岳阳';
首先MyCat启动时第一步会解析schema.xml文件中<table>标签的subTables属性(我们配置的是subTables=“t_user_00,t_user_01,t_user_02”)得到一个String数组[“t_user_00”,“t_user_01”,“t_user_02”];第二部根据配置的rule属性,判定采用枚举分片算法,从而会解析rule.xml和partition-hash-int.txt文件,生成一个Map集合(key:枚举值,value:数组索引);当执行上面的SQL语句时,MyCat根据rule.xml文件中配置的枚举策略的columns,解析到city字段,也就得到了“岳阳”这个值,根据这个值从Map中Get到数组索引,最后从String数组中获取到表名,然后重新组装SQL语句,调用JDBC连接进行查询。
关于The content of element type “mycat:rule” must match “(tableRule*,function*)”. 报错的说明:
MyCat的rule.xml配置文件里不支持tableRule/function/tableRule/function交叉的方式配置。只能是tableRule/function顺序配置。
单库分表之全局唯一性自增长ID
由于项目使用的分表字段为varchar类型,因此选择了采用枚举分表算法,还有一个重要的问题没有解决,那就是ID自增长问题和ID唯一性问题,那么到底如何解决呢?
其实,MyCat为我们提供了5种方式,各有优缺点:
生成方式 | 优点 | 缺点 |
---|---|---|
本地文件 | 本地加载,读取速度较快 | 当 MyCAT 重新发布后,配置文件中的 sequence 会恢复到初始值,高并发量请求存在瓶颈。 |
数据库 | 可以实现预读到MyCat内存,速度较之本地文件方式更快。并且ID是趋势递增的 | 存在临界值并发问题 |
本地时间戳 | 速度快,使用简单,ID趋势递增 | 并发量高时,存在问题,分布式集群时不可用 |
ZK ID生成器 | 具备速度、稳定性、安全性的解决方案,能抗并发 | ZK需要集群,实现复杂,需要花费额外成本维护Zookeeper。不支持ID递增 |
ZK 递增 | 同上+ID自增 | 同上 |
数据库方式原理
在数据库中建立一张表,存放 sequence 名称(name),sequence 当前值(current_value),步长(increment
int 类型每次读取多少个 sequence,假设为 K)等信息;
在这里说明一下临界值问题
MyCat数据库ID自增长方式,具备预读功能,基于上述数据库方式实现原理,每次读取数据库都会读取K个ID存入MyCat缓存中,当MyCat缓存中的ID使用到最后一个时,出现的并发情况则称为“临界值并发问题”。但非亿级流量(实际情况低于这个数值,具体需根据业务而论)难以满足出现此问题的条件。越复杂的业务,就需要越稳定的系统,就需要采用越复杂的技术实现,ZK才是王道。复杂唯一性自增ID方式可参考美团实现方案:https://tech.meituan.com/2019/03/07/open-source-project-leaf.html
数据库方式实现以及遇到的坑
之前说过公司网站流量并不高,因此经过对比采用了数据库方式实现。其他四种方式可参考MyCat官方进行使用:http://www.mycat.io/document/mycat-definitive-guide.pdf
1、首先server.xml文件新增sequnceHandlerType配置:
<system>
<property name="sequnceHandlerType">1</property>
</system>
2、创建mycat_sequence表
DROP TABLE IF EXISTS `mycat_sequence`;
CREATE TABLE `mycat_sequence` (
`name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'sequence名称',
`current_value` int(11) NOT NULL COMMENT '当前 value',
`increment` int(11) NOT NULL DEFAULT 100 COMMENT '增长步长! mycat 在数据库中一次读取多少个 sequence',
`remarks` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注',
PRIMARY KEY (`name`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '存放 sequence 的表' ROW_FORMAT = Dynamic;
新增数据
INSERT INTO `mycat_sequence`(`name`, `current_value`, `increment`, `remarks`) VALUES ('GLOBAL', 1, 100, 'MyCat默认');
INSERT INTO `mycat_sequence`(`name`, `current_value`, `increment`, `remarks`) VALUES ('COMPANY', 1, 100, NULL);
INSERT INTO `mycat_sequence`(`name`, `current_value`, `increment`, `remarks`) VALUES ('CUSTOMER', 1, 100, NULL);
INSERT INTO `mycat_sequence`(`name`, `current_value`, `increment`, `remarks`) VALUES ('ORDERS', 1, 100, NULL);
3、创建相关函数(三个)
这里应该使用项目中jdbc连接配置登陆的用户进行函数创建,此用户一般为非root用户。
- 用于获取当前 sequence 值的函数
CREATE DEFINER=`root`@`%` FUNCTION `mycat_seq_currval`(`seq_name` varchar(50)) RETURNS varchar(64) CHARSET utf8
DETERMINISTIC
BEGIN
#– 获取当前 sequence 的值 (返回当前值,增量)
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
- 获取下一个 sequence 值
CREATE DEFINER=`root`@`%` FUNCTION `mycat_seq_nextval`(`seq_name` varchar(50)) RETURNS varchar(64) CHARSET utf8
DETERMINISTIC
BEGIN
# 获取下一个 sequence 值
UPDATE MYCAT_SEQUENCE SET current_value = current_value + increment
WHERE NAME = seq_name;
RETURN mycat_seq_currval(seq_name);
END
- 设置 sequence 值
CREATE DEFINER=`root`@`%` FUNCTION `mycat_seq_setval`(`seq_name` varchar(50),`value` integer) RETURNS varchar(64) CHARSET utf8
DETERMINISTIC
BEGIN
#设置 sequence 值
UPDATE MYCAT_SEQUENCE
SET current_value = value
WHERE name = seq_name;
RETURN mycat_seq_currval(seq_name);
END
4、配置sequence_db_conf.properties
其中GLOBAL、COMPANY都是定义的列名,必须保证与mycat_sequence表name名称一致,dn1为schema.xml中的<dataNode>的name。
#sequence stored in datanode
GLOBAL=dn1
COMPANY=dn1
CUSTOMER=dn1
ORDERS=dn1
5、使用数据库方式后的insert语句
INSERT INTO `t_user_00`(`id`, `user_id`, `name`, `age`, `create_time`, `city`) VALUES (next value for MYCATSEQ_GLOBAL, 10001, '张三', 21, '2019-04-16 17:31:17', '岳阳');
next value for MYCATSEQ_GLOBAL表示为使用 名称为 GLOBAL的ID序列进行新增,对于不同表需要创建不同的序列,以用来防止ID冲突,保证唯一性。定义不同的序列只需要在mycat_sequence表添加一条数据,并在sequence_db_conf.properties配置文件中添加一个映射即可。
注意:
- sequence_db_conf.properties文件中的值必须保证与mycat_sequence表中的name一致,且不能存在特殊字符,如“-”,“_”之类。
- schema.xml文件中<table>标签必须具备primaryKey属性,且值必须为自增列名,如果需要得到自增后的ID则需要开启autoIncrement属性。敲黑板,划重点,primaryKey属性对应的MySQL数据列一定得是标有AUTO_INCREMENT的列,不然任会获取不到自增后的ID,原因是MyCat通过SELECT LAST_INSERT_ID();获取,而LAST_INSERT_ID();函数依赖于AUTO_INCREMENT标识。
<table name="t_user" primaryKey="id" autoIncrement="true" dataNode="dn1" subTables="t_user_00,t_user_01,t_user_02" rule="rule2"/>
<table name="t_no_database" dataNode="dn1" />
- 没有用到自增列属性的表则需要去掉primaryKey和autoIncrement属性,可在一定程度上提升性能
- schema.xml中的<schema>属性checkSQLschema必须设为false,设置为true表示不处理MyCat schema设定字符,将忽略SQL中的next value for MYCATSEQ_GLOBAL不进行处理。
单库分表踩过的坑总结
1、分表之间不能进行子查询、关联查询、in查询,分表可与非分表间进行简单的子查询、in查询、关联查询。
2、不要在schema 标签中添加 primaryKey=“ID” autoIncrement=“true” 否则会插入很慢
3、自增ID采用数据库实现方案时,新增需将自增列的值改为 next value for MYCATSEQ_对应的sequence,需添加是哪个指定的函数,需配置sequence_db_conf.properties
4、查询排序时,order by所使用到的排序字段必须在select中出现,否则将会报错:all columns in order by clause should be in the selected column list!createTime
5、MyCat的一些自带函数 sum,min,max,count等可以正确使用,但多分片执行的avg有bug,执行的结果是错误的
6、复杂删除 delete a from a join b on a.id=b.id; 支持子查询方式 delete from a where a.id in (select id from b), 但表不能起别名
7、在进行单表操作时,不能对表取别名,应遵循标准语法,否则MyCat在进行SQL的截取时会出现异常。
8、新增、修改mycat_sequence表数据后需要重启MyCat
9、<table>的primaryKey="id"指定主键,autoIncrement="true"开启新增返回自增主键,如非必要请勿开启,会影响性能
10、MyCat 对于表名大小写敏感,因此需注意MyCat配置中的表名与数据库表名必须统一大小写【schema.xml subTables属性】
11、对于第10点的补充,MyCat1.7版本,使用分片枚举进行单库分表【新增】时,如果分表字段未被匹配到对应的表名(注意是区分大小写的匹配),则会根据partition-hash-int.txt文件配置中的值为1的表中
12、(这个坑还得博主我花费了来调试,主要是问题产生于多线程中,且异常信息不明确)MyCat对 修改SQL语句中不支持 BigDecimal类型的0E-8【MathUtil.accuracyRoundDown工具类使用导致】(原值为0.00000000小数点后面8个0)遇到的时候加个判断,不然会出现mysql lock锁超时,当将timeout调整为>300s时会出现,MyCat事务不提交问题(MyCat默认数据库连接300s超时)
mycat.log日志输出异常:found backend connection SQL timeout ,close it MySQLConnection,表现症状为,数据库lock锁超时:
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction,尝试调整MySQL超时时间,修改my.cnf文件添加innodb_lock_wait_timeout=500,配置等待锁超时时间为500秒,
此时出现回滚异常,异常信息提示说:连接超时,系统尝试回滚时,连接已被断开!这是MyCat的异常,MyCat默认一个数据库连接的超时时间为300s,超过就会断开连接。
补充两条WebSocket需具备的机制
1、WebSocket前后端需具备心跳消息重连机制
2、客户端心跳,用于客户端重连;服务端心跳,用于维护session集合,无响应的客户端提出;默认三次,超过就踢出。
总结
根据目前业务需求,我们选择了枚举算法,基于在原有的代码改动最小的基础上优化交易所撮合性能,ShardingJDBC侵入性太强,对原有代码改动大,最重要的是使用复杂,配置复杂,成本高,而MyCat代理的实现方式更符合要求。至于为什么选择枚举模式,首要原因是分表字段是varchar类型。