读写分离方案
"1.0"?>
<!DOCTYPE mycat:schema SYSTEM "schema.dtd">
"http://io.mycat/">
<!-- 数据库配置 与server.xml中的数据库对应 即逻辑库 -->
"TESTDB" checkSQLschema="false" sqlMaxLimit="100" dataNode="db_node">
</schema>
<!-- 分片配置 即分片节点 -->
"db_node" dataHost="db_host" database="db_test" />
<!-- 物理数据库配置 即节点主机 -->
"db_host" maxCon="1000" minCon="10" balance="1"
writeType="0" dbType="mysql" dbDriver="native" switchType="1" slaveThreshold="100">
select user()</heartbeat>
<!-- can have multi write hosts -->
"hostM1" url="192.168.126.17:3305" user="root"
password="123456">
"hostS1" url="192.168.126.18:3304" user="root" password="123456" />
</writeHost>
</mycat:schema>
参数:
schema参数:
dataNode参数:
dataHost参数:
dataHost:数据主机(节点主机)
dbType:数据库驱动native:MySQL JDBC: oracle SQLServer
Balance参数设置:
WriteType参数设置:
switchType参数设置:
在此新窗口mycat,重启mycat:mycat restart
进入mycat数据库管理命令行:mysql -u root -p123456 -h 192.168.126.17 -P 8066
这-p和密码不能有空格
如果连接不上,可能是配置文件schema.xml配错了,或者mysql没启动等原因
在数据端口8066命令行使用TESTDB数据库:use TESTDB
从MyCat数据端口命令行插入一条数据:insert into dog values(2,@@hostname);
@@hostname是docker的容器id,因为是插入操作,所以是master的id。
双主双从,即将对方作为自己的master,自己作为对方的slave来进行复制,但对外来讲,还是一主一从。
一主一从,读写压力比较大会出现性能性问题;使用一主多从来解决,防止master单点故障;又使用双主双从来解决。
准备4台虚拟机或者在,搭建双主双从
不小心区分了端口号,但是没有影响,问题不大
第一台虚拟机的MySQL服务master1:docker run -d -p 3360:3306 -e MYSQL_ROOT_PASSWORD=123456 --name=master1 mysql:latest
第二台虚拟机的MySQL服务slave1:docker run -d -p 3370:3306 -e MYSQL_ROOT_PASSWORD=123456 --name=slave1 mysql:latest
第三台虚拟机的MySQL服务master2:docker run -d -p 3380:3306 -e MYSQL_ROOT_PASSWORD=123456 --name=master2 mysql:latest
第四台虚拟机的MySQL服务slave2:docker run -d -p 3390:3306 -e MYSQL_ROOT_PASSWORD=123456 --name=slave2 mysql:latest
修改master1配置文件
将mysql.cnf文件拷贝出来:docker cp master1:/etc/my.cnf .
修改配置文件my.cnf:vim my.cnf,添加如下配置:
[mysqld]
# 配置唯一ID
server-id=17
# 开启二进制日志
log-bin=mysql-bin
# 设置不要复制的数据库(可设置多个)
binlog-ignore-db=mysql
binlog-ignore-db=information_schema
# 设置logbin格式
binlog_format=STATEMENT
# 在作为从数据库的时候,有写入操作也要更新二进制文件
log-slave-updates
# 指自增字段的起始值,其默认值是1,取值范围是1~65535
auto_increment_increment=2
# 指字段一次递增多少,取值范围是1~65535
auto_increment_offset=1
将my.cnf文件拷贝回master1容器:docker cp my.cnf master1:/etc/
修改master2配置文件
将mysql.cnf文件拷贝出来:docker cp master2:/etc/my.cnf .
修改配置文件my.cnf:vim my.cnf,添加如下配置:
[mysqld]
# 配置唯一ID
server-id=19
# 开启二进制日志
log-bin=mysql-bin
# 设置不要复制的数据库(可设置多个)
binlog-ignore-db=mysql
binlog-ignore-db=information_schema
# 设置需要复制的数据库
#binlog-do-db=需要复制的master数据库
# 设置logbin格式
binlog_format=STATEMENT
# 在作为从数据库的时候,有写入操作也要更新二进制文件
log-slave-updates
# 指自增字段的起始值,其默认值是1,取值范围是1~65535
auto_increment_increment=2
# 指字段一次递增多少,取值范围是1~65535
auto_increment_offset=1
将my.cnf文件拷贝回master2容器:docker cp my.cnf master2:/etc/
修改slave1配置文件
将mysql.cnf文件拷贝出来:docker cp slave1:/etc/my.cnf .
修改配置文件my.cnf:vim my.cnf,添加如下配置:
[mysqld]
# 配置唯一ID
server-id=18
# 开启中继日志
relay-log=mysql-relay
将my.cnf文件拷贝回slave1容器:docker cp my.cnf slave1:/etc/
修改slave2配置文件
将mysql.cnf文件拷贝出来:docker cp slave2:/etc/my.cnf .
修改配置文件my.cnf:vim my.cnf,添加如下配置:
[mysqld]
# 配置唯一ID
server-id=20
# 开启中继日志
relay-log=mysql-relay
将my.cnf文件拷贝回slave2容器:docker cp my.cnf slave2:/etc/
重启所有MySQL服务:
docker restart master1docker restart master2docker restart slave1docker restart slave2进入容器:docker exec -it 容器名或id bash
进入docker容器后只显示bash-4.4,则输入:cp /etc/skel/.bash* /root/
exit退出容器,再进容器就可以了。
如果此时若容器没有启动,可能是配置文件有问题。
在两个master中,进入到Mysql命令行,创建数据同步用户slave:
CREATE USER slave IDENTIFIED BY '123456';
GRANT REPLICATION SLAVE ON *.* TO 'slave'@'%';
GRANT ALL PRIVILEGES ON *.* TO 'slave'@'%';
FLUSH PRIVILEGES;
因为版本是大于8.0.4的,需要在两个master上更换密码机制并修改密码:ALTER USER 'slave'@'%' IDENTIFIED WITH mysql_native_password BY '123456';
查看用户的密码机制:select user,host,plugin from user;
查看master1的状态:show master status;
从库slave1连接主库master1,在从库slave1中的mysql命令行执行:change master to master_host='192.168.126.17',master_user='slave',master_port=3360,master_password='123456',master_log_file='mysql-bin.000007',master_log_pos=157;
启动复制功能:start slave;
查看master2的状态:show master status;
从库slave2连接主库master2,在从库slave2中的mysql命令行执行:change master to master_host='192.168.126.19',master_user='slave',master_port=3380,master_password='123456',master_log_file='mysql-bin.000006',master_log_pos=157;
启动复制功能:start slave;
查看连接状态:show slave status \G;
当两个线程都显示yes时,配置主从复制成功。
配置master1成为master2的slave
在master1的mysql命令行,配置为master2的slave:change master to master_host='192.168.126.19',master_user='slave',master_port=3380,master_password='123456',master_log_file='mysql-bin.000007',master_log_pos=157;
配置master2成为master1的slave
在master2的mysql命令行,配置为master1的slave:change master to master_host='192.168.126.17',master_user='slave',master_port=3360,master_password='123456',master_log_file='mysql-bin.000008',master_log_pos=157;
启动复制功能:start slave;
查看连接状态:show slave status \G;
当显示Slave_IO_Running: Yes,Slave_SQL_Running: Yes时,配置成功。
进入到/usr/local/mycat/conf目录下,编辑schema.xml文件:vim schema.xml,配置如下:
"1.0"?>
<!DOCTYPE mycat:schema SYSTEM "schema.dtd">
"http://io.mycat/">
<!-- 数据库配置 与server.xml中的数据库对应 即逻辑库 -->
"TESTDB" checkSQLschema="false" sqlMaxLimit="100" dataNode="db_node">
</schema>
<!-- 分片配置 即分片节点 -->
"db_node" dataHost="db_host" database="db_test" />
<!-- 物理数据库配置 即节点主机 -->
"db_host" maxCon="1000" minCon="10" balance="1"
writeType="0" dbType="mysql" dbDriver="native" switchType="1" slaveThreshold="100">
select user()</heartbeat>
<!-- 读写分离 双主双从 -->
"hostM1" url="192.168.126.17:3360" user="root" password="123456">
"hostS1" url="192.168.126.18:3370" user="root" password="123456" />
</writeHost>
"hostM2" url="192.168.126.19:3380" user="root" password="123456">
"hostS2" url="192.168.126.20:3390" user="root" password="123456" />
</writeHost>
</dataHost>
</mycat:schema>
即在一主一从的配置基础上,再配置另一个主从。
启动mycat:mycat start
目前很多互联网系统都存在单表数据量过大的问题,这就降低了查询数据,影响了客户体验。为了提高查询效率,可以优化SQL语句,优化表结构和索引,不过对那些百万级千万级的数据库表,即便是优化过后,查询速度还是满足不了要求。
垂直分割是指数据表列的拆分,把一张列比较多的表拆分为多张表。表的记录并不多,但是字段却很长,表占用空间很大,检索表的时候需要执行大量的IO,严重降低了性能。这时需要把大的字段拆分到另一个表,并且该表与原表是一对一的关系。
拆分原则:
修改mycat目录下的schema.xml配置文件,配置如下:
"1.0"?>
<!DOCTYPE mycat:schema SYSTEM "schema.dtd">
"http://io.mycat/">
<!-- 数据库配置 与server.xml中的数据库对应 即逻辑库 -->
"TESTDB" checkSQLschema="false" sqlMaxLimit="100" dataNode="dn1">
<!-- 垂直拆分 -->
"customer" dataNode="dn2"/>
</schema>
<!-- 分片配置 即分片节点 实现垂直分库 -->
"dn1" dataHost="host1" database="orders" />
"dn2" dataHost="host2" database="orders" />
<!-- 物理数据库配置 即节点主机 -->
"host1" maxCon="1000" minCon="10" balance="1"
writeType="0" dbType="mysql" dbDriver="native" switchType="1" slaveThreshold="100">
select user()</heartbeat>
"hostM1" url="192.168.126.17:3360" user="root" password="123456">
</writeHost>
</dataHost>
"host2" maxCon="1000" minCon="10" balance="1"
writeType="0" dbType="mysql" dbDriver="native" switchType="1" slaveThreshold="100">
select user()</heartbeat>
"hostM1" url="192.168.126.19:3380" user="root" password="123456">
</writeHost>
</dataHost>
</mycat:schema>
- 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
-
停止之前两个master的相互复制,都在mysql命令行中执行:stop slave;
-
两个master同时创建数据库orders:create database orders;
-
将两个master的密码机制都改为mysql_native_password:ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY '123456';
这样MyCat才能访问到,否则密码不正确,因为Mysql是最新版的
-
重启mycat:mycat restart
-
进入到master1的Mysql命令行:mysql -u root -p123456 -h 192.168.126.17 -P 8066
-
在MyCat中,使用数据库TESTDB:use TESTDB;
-
创建表customer:create table customer(id int auto_increment,name varchar(200),primary key(id));
-
创建表orders:create table orders(id int auto_increment,order_type int,customer_id int,amount decimal(10,2),primary key(id));
-
创建表orders_detail:create table orders_detail(id int auto_increment,order_id int,detail varchar(200),primary key(id));
-
创建表dict_orders_type:create table dict_orders_type(id int auto_increment,order_type varchar(200),primary key(id));
-
此时在mycat的TESEDB逻辑库中,创建4个表,但是customer表被创建到master2的orders,其他都在master1的orders中,实现了分库。
即指定customer表都在master2的orders中执行,其余都在master1的orders中执行。
2.水平拆分-分表
- 垂直拆分只是把表按模块分到不同数据库,但没有解决单表大数据量的问题。
- 水平拆分,相对于垂直拆分水平拆分不是将表做分类,而是按照某个字段的某种规则来分散到多个库之中,每个表中包含一部分数据。
数据的水平切分是按照数据行的切分,就是将表中的某些行切分到一个数据库,而将另外某些行又切分到其他数据库中。
实现分表:
-
选择要拆分的表
MySQL单表存储条数是有瓶颈的,单表达到1000W条就达到了瓶颈,会影响查询效率,需要进行水平拆分(分表)来优化。
-
分表字段
orders表为例,可以根据不同字段进行分表。
字段id:查询订单注重时效,历史订单被查询的次数少,如此分片会造成一个节点访问多,一个节点访问少,不平均。
字段customer_id:根据客户id去分,两个节点访问平均。
-
修改配置文件schema.xml
<!-- 水平拆分 -->
"orders" dataNode="dn1,dn2" rule="mod-rule" />
- 1
- 2
即为orders表设置数据节点为dn1、dn2,并指定分片规则为mod-rule(自定义)
-
修改配置文件rule.xml
- 分片规则:在rule配置文件里新增分片规则mod-rule,并指定规则适用字段customer_id,还有选择分片算法mod_long(对字段求模运算),customer_id对两个节点求模,根据结果分片。配置算法mod-long参数count为2,两个节点
- 代码:
"mod-rule">
customer_id</columns>
mod-long</algorithm>
</rule>
</tableRule>
<function name="mod-long" class="io.mycat.route.function.PartitionByMod">
<!-- how many data nodes -->
"count">2</property>
</function>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
-
在master2中的orders数据库中创建orders表
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
- 7
-
重启mycat,生效配置:mycat restart
-
在mycat的8066端口插入数据,测试是否实现水平拆分
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);
- 1
- 2
- 3
- 4
- 5
- 6
在master1和master通过select查看分片是否正确即可。
3.ER表
-
问题,orders表分片了,那和它相关的orders_detail表未分片,join联查时,master1正常查询出结果,master2由于没有orders_detail表,则报错,最后聚合结果也是错的。
-
ER表,将子表的存储位置依赖于主表,并且物理上紧邻存放,因此彻底解决了JOIN的效率和性能问题,根据这一思路,提出了基于E-R关系的数据分片策略,子表的记录与所关联的父表记录存放在同一个数据分片上。
-
修改配置文件schema.xml,代码如下:
"orders" dataNode="dn1,dn2" rule="mod-rule" >
<!-- ER表 -->
"orders_detail" primaryKey="id" joinKey="order_id" parentKey="id" />
</table>
- 1
- 2
- 3
- 4
即在原先的table下添加一个子表childTable。
-
在master2中的orders数据库中创建orders_detail表:
create table orders_detail(
id int auto_increment,
order_id int,
detail varchar(200),
primary key(id)
);
- 1
- 2
- 3
- 4
- 5
- 6
-
重启Mycat:mycat restart
-
进入到8066端口,在mycat中插入orders_detail数据:
insert into orders_detail(detail, order_id) values('detail1',1);
insert into orders_detail(detail, order_id) values('detail1',2);
insert into orders_detail(detail, order_id) values('detail1',3);
insert into orders_detail(detail, order_id) values('detail1',4);
insert into orders_detail(detail, order_id) values('detail1',5);
- 1
- 2
- 3
- 4
- 5
-
在Mycat中使用join来测试ER表配置是否成功:select a.*,b.detail from orders a join orders_detail b on a.id=b.order_id;
4.全局表
- 全局表,在分片的情况下,当业务表因为规模而进行分片以后,业务表与这些附属的字典表之间的关联,就成了比较棘手的问题,字典表具有以下特性:
- 变动不频繁
- 数据量总体变化不大
- 数据规模不大,很少有超过10W条记录
- 全局表特征:
- 全局表插入更新操作会实时在所有节点上执行,保持各个分片的数据一致。
- 全局表的查询操作,只从一个节点获取。
- 全局表可以和任何一个表进行JOIN操作。
- 将字典表或者符合字典表特性的一些表定义为全局表,则从另外一个方面,很好的解决了数据JOIN的难题。通过全局表+基于E-R关系的分片策略,可以满足80%的企业应用开发。
- 在master2的orders数据库中创建dict_order_type表:
create table dict_order_type(
id int auto_increment,
order_type varchar(200),
primary key(id)
);
- 1
- 2
- 3
- 4
- 5
-
在master1修改一下表名: alter table dict_orders_type rename to dict_order_type;
之前创建table错了好像,没有错就不用改名字
-
在schema.xml配置全局表,如下
<!-- 全局表 -->
"dict_order_type" dataNode="dn1,dn2" type="global"></table>
- 1
- 2
- 3
-
重启MyCat:mycat restart
-
重新进入8066端口,插入数据:
insert into dict_order_type(id,order_type) values(101,'type1');
insert into dict_order_type(id,order_type) values(102,'type2');
- 1
- 2
-
此时在master1和master2都查询得到插入的两条记录:select * from dict_order_type;
三、MyCat分片规则
1. 取模分片
- 取模分片就是根据数据表的某一个字段,通常是某一个整数型的字段,对其进行十进制的取模运算,将运算结果作为MyCat的路由结果。
- 优点:这种策略可以很好的分散数据库写的压力。
- 缺点:出现了范围查询,就需要MyCat去合并结果,当数据量偏高的时候,这种夸库查询+合并结果消耗的时间可能会增加很多,尤其是还出现order by的时候。
- tableRule标签,定义表规则
- name:指定唯一名字,用于标识不同的表规则。内嵌的rule标签则指定对物理表中的哪一列进行拆分和使用什么路由算法。
- columns:指定要拆分的列名字。
- algorithm:使用function标签的name属性。连接表规则和具体路由算法。多个表规则(tableRule)可以连接到同一个路由算法(function)上。
- function标签,定义具体路由算法
- name:指定算法的名字
- class:指定路由算法具体的类名字
- property:具体算法需要用的一些属性
- count:表示需要取模的最大值,将数据分成该配置的切片
2. 枚举分片
-
实现原理,有些业务需要按照省份或区县来做保存,这类业务使用本条规则。
-
实现过程,需要定义三个值,规则均是在rule.xml中定义。
- tableRule
- function
- mapFile
-
在master1和master2的orders数据库中,创建示例表orders_ware_info
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)
);
- 1
- 2
- 3
- 4
- 5
- 6
- 7
-
修改schema.xml文件
<!-- 枚举分片 -->
"orders_ware_info" dataNode="dn1,dn2" rule="sharding-by-intfile" />
- 1
- 2
即表orders_ware_info,按照规则sharding-by-intfile进行分片
-
修改rule.xml文件
<!-- 枚举分片 -->
"sharding-by-intfile">
areacode</columns>
hash-int</algorithm>
</rule>
</tableRule>
<function name="hash-int" class="io.mycat.route.function.PartitionByFileMap">
"mapFile">partition-hash-int.txt</property>
"type">1</property>
"defaultNode">0</property>
</function>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
即按照字段areacode进行枚举分片。
参数:
- mapFile:指的是配置文件名。
- type:默认值为0,0表示Integer,非零表示String。
- defaultNode 默认节点:小于0表示不设置默认节点,大于等于0表示设置默认节点 默认节点的作用:枚举分片时,如果碰到不识别的枚举值,就让它路由到默认节点。
-
修改partition-hash-int.txt文件
10086=0
10010=1
- 1
- 2
0则代表第一个节点,1则代表第二个节点
-
重启mycat:mycat restart
-
进入8066端口,使用TESTDB数据库,插入2条数据:
INSERT INTO orders_ware_info(id, order_id,address,areacode) VALUES (1,1,'北京','10086');
INSERT INTO orders_ware_info(id, order_id,address,areacode) VALUES (2,2,'天津','10010');
- 1
- 2
3.范围分片
-
实现原理,此分片适用于提前规划好分片字段的某个范围属于哪个分片。
- 优点:适用于想明确知道某个分片字段的某个范围具体在哪一个节点。
- 缺点:如果短时间内有大量的批量插入操作,那么某个分片节点可能一下会承受比较大的数据库压力,而别的分片节点此时可能处于闲置状态,无法利用其他节点进行分担压力(热点数据问题)。
-
在master1和master2的orders数据库中,创建示例表payment_info
CREATE TABLE payment_info
(
`id` INT AUTO_INCREMENT comment '编号',
`order_id` INT comment '订单编号',
`payment_status` INT comment '支付状态',
PRIMARY KEY(id)
);
- 1
- 2
- 3
- 4
- 5
- 6
- 7
-
修改配置文件schema.xml
<!-- 范围分片 -->
"payment_info" dataNode="dn1,dn2" rule="auto-sharding-long" />
- 1
- 2
-
修改配置文件rule.xml
<!-- 范围分片 -->
"auto-sharding-long">
order_id</columns>
rang-long</algorithm>
</rule>
</tableRule>
<function name="rang-long" class="io.mycat.route.function.AutoPartitionByLong">
"mapFile">autopartition-long.txt</property>
</function>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
参数
- mapFile:指的是配置文件名
- type:默认值为0,0表示Integer,非零表示String。因为我接下来的测试是基于省份分片,所以需type指定为1。
- defaultNode 默认节点:小于0表示不设置默认节点,大于等于0表示设置默认节点 默认节点的作用:枚举分片时,如果碰到不识别的枚举值,就让它路由到默认节点。
-
修改文件autopartition-long.txt
0-102=0
103-200=1
- 1
- 2
-
重启mycat:mycat restart
-
在8066端口插入数据用来测试:
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);
- 1
- 2
- 3
- 4
4.按日期(天)分片
-
实现原理,此规则按天分片,设定时间格式、范围
-
在master1和master2中创建示例表login_info
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
-
修改配置文件schema.xml
<!-- 日期(天)分片 -->
"login_info" dataNode="dn1,dn2" rule="sharding-by-date" />
- 1
- 2
-
修改配置文件rule.xml
<!-- 日期(天)分片 -->
"sharding-by-date">
login_date</columns>
partbyday</algorithm>
</rule>
</tableRule>
<!-- 日期(天)分片 -->
<function name="partbyday" class="io.mycat.route.function.PartitionByDate">
"dateFormat">yyyy-MM-dd</property>
"sNaturalDay">0</property>
"sBeginDate">2022-01-01</property>
"sEndDate">2022-01-04</property>
"sPartionDay">2</property>
</function>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
参数
- columns:分片字段,algorithm:分片函数
- dateFormat:日期格式
- sBeginDate:开始日期
- sEndDate:结束日期,则代表数据达到了这个日期的分片后循环从开始分片插入
- sPartionDay:分区天数,即默认从开始日期算起,每隔 2 天一个分区
-
重启mycat:mycat restart
-
在8066端口插入数据用来测试:
insert into login_info(id,user_id,login_date) values(1,101,'2022-01-01');
insert into login_info(id,user_id,login_date) values(2,102,'2022-01-02');
insert into login_info(id,user_id,login_date) values(3,103,'2022-01-03');
insert into login_info(id,user_id,login_date) values(4,104,'2022-01-04');
insert into login_info(id,user_id,login_date) values(5,105,'2022-01-05');
insert into login_info(id,user_id,login_date) values(6,106,'2022-01-06');
- 1
- 2
- 3
- 4
- 5
- 6
5.全局序列
- 在实现分库分表的情况下,数据库自增主键已无法保证全局唯一。
解决方案
-
本地文件方式
此方式MyCat将sequence配置到文件中,当使用到sequence中的配置后,MyCat会更新classpath中的sequence_conf.properties文件中sequence当前的值。
- 优点:本地加载,读取速度较快
- 缺点:抗风险能力差,MyCat所在主机宕机后,无法读取本地文件。
-
本地时间戳方式
全局序列ID=64位二进制(42(毫秒)+ 5(机器ID)+ 5(业务编码)+ 12(重复累加))换算成十进制,为18位数的long类型,每毫秒可以并发12位二进制的累加
- 优点:配置简单
- 缺点:18位ID过长
-
数据库方式
利用数据库的一个表来进行计数累加。但是并不是每次生成序列都读写数据库,这样效率太低。MyCat会预加载一部分号段到MyCat内存中,这样大部分读写序列都是在内存中完成的。如果内存中的号段用完了,MyCat会再向数据库要一次。
原理:在数据库中建立一张表,存放sequence名称(name),sequence当前值(current_value),步长(increment int类型每次读取多少个sequence,假设为K)等信息。
数据库解决全局序列
-
修改MyCat配置文件server.xml
<!-- 全局序列类型:0-本地文件,1-数据库方式,2-时间戳方式 -->
"sequenceHandlerType">1</property>
- 1
- 2
-
修改配置文件schema.xml
<!-- 全局序列-数据库方式 -->
"test" primaryKey="id" autoIncrement="true" dataNode="dn1,dn2" rule="mod-long" />
"mycat_sequence" primaryKey="name" dataNode="dn2" />
- 1
- 2
- 3
即前面一个表用来做测试,后面一个表就是存放sequence的表。
-
修改文件sequence_db_conf.properties
GLOBAL=dn1
COMPANY=dn1
CUSTOMER=dn1
ORDERS=dn1
MYCAT=dn2
- 1
- 2
- 3
- 4
- 5
-
在master2的orders数据库中添加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;
- 1
- 2
-
MYCAT_SEQUENCE表插入初始记录
INSERT INTO MYCAT_SEQUENCE(name,current_value,increment) VALUES ('mycat', -99, 100);
- 1
代表插入了一个名为mycat的sequence,当前值为-99,步长为100。
-
在master2的orders数据库中创建全局序列所需存储过程
- 获取当前sequence的值
DELIMITER $$
CREATE FUNCTION mycat_seq_currval(seq_name VARCHAR(50)) RETURNS VARCHAR(64)
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 ;
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 设置sequence的值
DELIMITER $$
CREATE FUNCTION mycat_seq_setval(seq_name VARCHAR(50),VALUE INTEGER) RETURNS
VARCHAR(64)
DETERMINISTIC
BEGIN
UPDATE MYCAT_SEQUENCE
SET current_value = VALUE
WHERE NAME = seq_name;
RETURN mycat_seq_currval(seq_name);
END $$
DELIMITER ;
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 获取下一个sequence的值
DELIMITER $$
CREATE FUNCTION mycat_seq_nextval(seq_name VARCHAR(50)) RETURNS VARCHAR(64)
DETERMINISTIC
BEGIN
UPDATE MYCAT_SEQUENCE
SET current_value = current_value + increment WHERE NAME = seq_name;
RETURN mycat_seq_currval(seq_name);
END $$
DELIMITER ;
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
-
重启mycat:mycat restart
-
在master1和master2中创建test表:
create table test(id int,name varchar(10));
- 1
-
在MyCat中向test表中添加测试数据
insert into test(id,name) values(next value for MYCATSEQ_MYCAT,(select database()));
- 1
-
查询数据进行验证,看是否自增:SELECT * FROM test order by id asc;
总结:
- MyCat的读写分离及自动切换技术,是基于MySQL的主从复制技术实现的。
MySQL主从复制解决数据一致性的问题。MyCat的读写分离主要是为了实现高性能。
在schema.xml配置文件中配置逻辑库等,需要跟server.xml中的schema对应。 - MySQL双主双从,实现了读写分离、高可用、高性能,即两个readHost都可以对外提供读操作;而两个writeHost中,第一个writeHost对外提供写操作,第二个writeHost为第一个的备机。在一主多从到双主双从,可以减少单个writeHost的复制压力(即高性能),也实现了写操作的高可用。
它们的结构则是两个主从,并且这两个主会相互成为对方的从。 - 垂直拆分,即将不常用字段和大字段进行拆分,将常用的组合查询的列放在一张表。
垂直拆分用于分库,在配置文件schema.xml中配置,即schame参数有一个分片节点,当不指定表给某个节点时,全部的SQL都交由这个节点处理;有指定则将指定表的SQL交由指定节点处理。指定节点会有一个对应的节点主机dataHost。
MyCat垂直拆分主要解决性能问题。 - 水平拆分,数据的水平切分是按照数据行的切分,就是将表中的某些行切分到一个数据库,而将另外某些行又切分到其他数据库中。
需要在schema.xml配置需要水平切分的表,切分到哪个DataNode,以及分片规则rule的名字,对应到rule.xml中配置。
配置相比于垂直切分,水平切分多配置了一个rule.xml;在主机层面,垂直切分,将表按字段切分到多个主机的同一个数据库名,水平切分,将表按行切分到多个主机的同一个数据库名。
MyCat水平拆分主要解决表数据量大的问题。 - ER表,解决JOIN的效率和性能问题,即用来处理表与子表关联查询的问题,在schema.xml中配置,在表中添加一个子表childTable。
即水平拆分时,行数据会被切分,在joid查询时,又需要数据库中存在对应的子表行数据,所以使用ER表来让子表的行数据跟随对应表的行数据。即joinKey与parentKey的值相同的会被分配在同一个主机的同一个数据库中。 - 全局表,即所有分片节点都有的一个表,并且插入更新操作在所有节点实时执行。
主要解决变动不频繁和数据量总体不大的数据,通过type="global"来设置全局表。 - 分片规则有取模、枚举、范围、日期、全局序列等。
分片枚举指的是需要按照省份或者区县来做保存的。
范围分片缺点是处理热点数据问题。
全局序列解决分表分库后id不唯一的问题。
通过设置server.xml的sequenceHandlerType为1(即以数据库的方式)来实现全局序列。
-
相关阅读:
《c++ Primer Plus 第6版》读书笔记(4)
数据结构与算法-希尔排序
江苏服务器租用风险有哪些?
解密地理位置模拟攻防之道
Docker项目部署lnmp+wordpress
URL与URI小结
LeetCode刷题day24||回溯算法理论基础&&77. 组合--回溯
LeetCode2409——统计共同度过的日子数
C/C++书籍信息系统
谷粒商城 高级篇 (十三) --------- 异步&线程池
-
原文地址:https://blog.csdn.net/weixin_49076273/article/details/126634443