mysql 高级 12-Mycat 分库分表

2021年02月07日 15:15 · 阅读(73) ·

参考

Mycat教程—-数据库的分库分表

mycat配置分库分表

前提

mysql 高级 10-Mycat 安装

开发环境

第一台机器-Linux

由于条件限制,Mycat 也安装在这台机器

由于条件限制,Mycat 也安装在这台机器

名称 版本
CentOS7 CentOS-7-x86_64-DVD-2003.iso
Linux 3.10.0-1127.el7.x86_64
mysql 5.7.16
IP 192.168.114.128

第二台机器-Windows

名称 版本
操作系统 Windows 10 X64
mysql 8.0.11
IP 192.168.114.1

测试库

两台机器都存在的数据库 luoma_test_1

分库

测试表-customer

配置 customer 表只在 192.168.114.1 这台机器的数据库上使用

  1. CREATE TABLE customer(
  2. id INT AUTO_INCREMENT,
  3. NAME VARCHAR(200),
  4. PRIMARY KEY(id)
  5. );
  6. insert into customer(`name`) values('name1');
  7. insert into customer(`name`) values('name2');

修改 schema.xml

  1. <?xml version="1.0"?>
  2. <!DOCTYPE mycat:schema SYSTEM "schema.dtd">
  3. <mycat:schema xmlns:mycat="http://io.mycat/">
  4. <schema name="TESTDB" checkSQLschema="false" sqlMaxLimit="100" dataNode="dn1">
  5. <table name="customer" dataNode="dn2" ></table>
  6. </schema>
  7. <dataNode name="dn1" dataHost="host1" database="luoma_test_1" />
  8. <dataNode name="dn2" dataHost="host2" database="luoma_test_1" />
  9. <dataHost name="host1" maxCon="1000" minCon="10" balance="1"
  10. writeType="0" dbType="mysql" dbDriver="native" switchType="1" slaveThreshold="100">
  11. <heartbeat>select user()</heartbeat>
  12. <writeHost host="hostM1" url="192.168.114.128:3306" user="root"
  13. password="123456">
  14. <!-- 读写分离; 写走 hostM1,读走 hostS1; hostM1 宕机了, hostS1 也不可用 -->
  15. <readHost host="hostS1" url="192.168.114.1:3306" user="root"
  16. password="123456">
  17. </readHost>
  18. </writeHost>
  19. <!-- 高可用,hostM1宕机了, hostM2顶上 -->
  20. <writeHost host="hostM2" url="192.168.114.1:3306" user="root" password="123456" />
  21. </dataHost>
  22. <dataHost name="host2" maxCon="1000" minCon="10" balance="0"
  23. writeType="0" dbType="mysql" dbDriver="native" switchType="1" slaveThreshold="100">
  24. <heartbeat>select user()</heartbeat>
  25. <writeHost host="host2M1" url="192.168.114.1:3306" user="root"
  26. password="123456">
  27. </writeHost>
  28. </dataHost>
  29. </mycat:schema>

测试

进入 Macat mysql -uroot -p123456 -P8066 -h192.168.114.128

查询 customer

  1. mysql> select * from customer;
  2. +----+-------+
  3. | id | NAME |
  4. +----+-------+
  5. | 1 | name1 |
  6. | 2 | name2 |
  7. +----+-------+
  8. 2 rows in set (0.25 sec)

水平分表

测试表-orders

orders 表根据 customer_id 不同放到不同的数据库中

两台机器的 luoma_test_1 数据库都创建这张表

  1. CREATE TABLE orders(
  2. id INT AUTO_INCREMENT,
  3. order_type INT,
  4. customer_id INT,
  5. amount DECIMAL(10,2),
  6. PRIMARY KEY(id)
  7. );

修改 schema.xml

vim /usr/local/mycat/conf/schema.xml

  1. <?xml version="1.0"?>
  2. <!DOCTYPE mycat:schema SYSTEM "schema.dtd">
  3. <mycat:schema xmlns:mycat="http://io.mycat/">
  4. <schema name="TESTDB" checkSQLschema="false" sqlMaxLimit="100" dataNode="dn1">
  5. <table name="customer" dataNode="dn2"></table>
  6. <table name="orders" dataNode="dn1,dn2" rule="mod_rule" ></table>
  7. </schema>
  8. <dataNode name="dn1" dataHost="host1" database="luoma_test_1" />
  9. <dataNode name="dn2" dataHost="host2" database="luoma_test_1" />
  10. <dataHost name="host1" maxCon="1000" minCon="10" balance="2" writeType="0" dbType="mysql" dbDriver="native" switchType="1" slaveThreshold="100">
  11. <heartbeat>select user()</heartbeat>
  12. <writeHost host="host1M1" url="192.168.114.128:3306" user="root" password="123456">
  13. </writeHost>
  14. </dataHost>
  15. <dataHost name="host2" maxCon="1000" minCon="10" balance="0" writeType="0" dbType="mysql" dbDriver="native" switchType="1" slaveThreshold="100">
  16. <heartbeat>select user()</heartbeat>
  17. <writeHost host="host2M1" url="192.168.114.1:3306" user="root" password="123456">
  18. </writeHost>
  19. </dataHost>
  20. </mycat:schema>

修改 rule.xml

vim /usr/local/mycat/conf/rule.xml

  1. <tableRule name="mod_rule">
  2. <rule>
  3. <columns>customer_id</columns>
  4. <algorithm>mod-long</algorithm>
  5. </rule>
  6. </tableRule>
  7. <function name="mod-long" class="io.mycat.route.function.PartitionByMod">
  8. <!-- how many data nodes -->
  9. <property name="count">2</property>
  10. </function>

重启 Macat

  1. mycat restart

Mycat 添加 orders 表数据

mysql -uroot -p123456 -P8066 -h192.168.114.128

  1. insert into orders(id,order_type,customer_id,amount) values(1,101,100,100100);
  2. INSERT INTO orders(id,order_type,customer_id,amount) VALUES(2,101,100,100300);
  3. INSERT INTO orders(id,order_type,customer_id,amount) VALUES(3,101,101,120000);
  4. INSERT INTO orders(id,order_type,customer_id,amount) VALUES(4,101,101,103000);
  5. INSERT INTO orders(id,order_type,customer_id,amount) VALUES(5,102,101,100400);
  6. INSERT INTO orders(id,order_type,customer_id,amount) VALUES(6,102,100,100020);

第一台机器的 orders 表数据

可以看到,根据 customer_id 字段进行了存储

第二台机器的 orders 表数据

可以看到,根据 customer_id 字段进行了存储

Mycat 查询

mysql -uroot -p123456 -P8066 -h192.168.114.128

可以看到,Mycat 把两个数据库两张表的数据进行了合并。

  1. mysql> select * from orders;
  2. +----+------------+-------------+-----------+
  3. | id | order_type | customer_id | amount |
  4. +----+------------+-------------+-----------+
  5. | 1 | 101 | 100 | 100100.00 |
  6. | 2 | 101 | 100 | 100300.00 |
  7. | 6 | 102 | 100 | 100020.00 |
  8. | 3 | 101 | 101 | 120000.00 |
  9. | 4 | 101 | 101 | 103000.00 |
  10. | 5 | 102 | 101 | 100400.00 |
  11. +----+------------+-------------+-----------+
  12. 6 rows in set (0.10 sec)

ER 分片

有一类业务,例如订单(orders)跟订单明细(orders_detail) ,明细表会依赖于订单,也就是说会存在表的主
从关系,这类似业务的切分可以抽象出合适的切分规则,比如根据用户ID(customer_id)切分,其他相关的表都依赖于用户ID。

再或者根据订单 ID 切分,总之部分业务总会可以抽象出父子关系的表。这类表适用于 ER 分片表,子表的记录与所关联的父表记录存放在同一个数据分片上,避免数据 Join 跨库操作。

测试表-orders_detail

  1. create table orders_detail(
  2. id INT AUTO_INCREMENT,
  3. detail varchar(2000),
  4. order_id int,
  5. PRIMARY KEY(id)
  6. );

在两台机器上分别创建表

修改 schema.xml

vim /usr/local/mycat/conf/schema.xml

  1. <?xml version="1.0"?>
  2. <!DOCTYPE mycat:schema SYSTEM "schema.dtd">
  3. <mycat:schema xmlns:mycat="http://io.mycat/">
  4. <schema name="TESTDB" checkSQLschema="false" sqlMaxLimit="100" dataNode="dn1">
  5. <table name="customer" dataNode="dn2"></table>
  6. <table name="orders" dataNode="dn1,dn2" rule="mod_rule" >
  7. <childTable name="orders_detail" primaryKey="id" joinKey="order_id" parentKey="id" />
  8. </table>
  9. </schema>
  10. <dataNode name="dn1" dataHost="host1" database="luoma_test_1" />
  11. <dataNode name="dn2" dataHost="host2" database="luoma_test_1" />
  12. <dataHost name="host1" maxCon="1000" minCon="10" balance="0" writeType="0" dbType="mysql" dbDriver="native" switchType="1" slaveThreshold="100">
  13. <heartbeat>select user()</heartbeat>
  14. <writeHost host="host1M1" url="192.168.114.128:3306" user="root" password="123456">
  15. </writeHost>
  16. </dataHost>
  17. <dataHost name="host2" maxCon="1000" minCon="10" balance="0" writeType="0" dbType="mysql" dbDriver="native" switchType="1" slaveThreshold="100">
  18. <heartbeat>select user()</heartbeat>
  19. <writeHost host="host2M1" url="192.168.114.1:3306" user="root" password="123456">
  20. </writeHost>
  21. </dataHost>
  22. </mycat:schema>

重启 Mycat 服务 mycat restart

测试-父表和子表数据不在一个分片上

1.第一台机器

现在第一台机器的 orders 数据如下

orders_detail 的数据如下

  1. insert into orders_detail(detail,order_id) values('订单 3 详情',3);
  2. insert into orders_detail(detail,order_id) values('订单 4 详情',4);

2.第二台机器

第二台机器的 orders 表数据如下

orders_detail 的数据如下

  1. insert into orders_detail(detail,order_id) values('订单 1 详情',1);
  2. insert into orders_detail(detail,order_id) values('订单 2 详情',2);

3.Mycat 查询

mysql -uroot -p123456 -P8066 -h192.168.114.128

  1. mysql> select t1.*,t2.detail from orders t1,orders_detail t2 where t1.id = t2.order_id;
  2. Empty set (0.01 sec)

如果出现了同一台机器,子表的记录和所关联的父表就没有在同一个数据分片上,就会出现数据丢失。

测试-父表和子表数据在一个分片上

1.第一台机器

现在第一台机器的 orders 数据如下

orders_detail 的数据如下

  1. delete from orders_detail;
  2. insert into orders_detail(detail,order_id) values('订单 1 详情',1);
  3. insert into orders_detail(detail,order_id) values('订单 2 详情',2);

2.第二台机器

第二台机器的 orders 表数据如下

orders_detail 的数据如下

  1. delete from orders_detail;
  2. insert into orders_detail(detail,order_id) values('订单 4 详情',4);
  3. insert into orders_detail(detail,order_id) values('订单 5 详情',5);

3.Mycat 查询

mysql -uroot -p123456 -P8066 -h192.168.114.128

  1. mysql> select t1.*,t2.detail from orders t1,orders_detail t2 where t1.id = t2.order_id;
  2. +----+------------+-------------+-----------+-----------------+
  3. | id | order_type | customer_id | amount | detail |
  4. +----+------------+-------------+-----------+-----------------+
  5. | 4 | 101 | 101 | 103000.00 | 订单 4 详情 |
  6. | 5 | 102 | 101 | 100400.00 | 订单 5 详情 |
  7. | 1 | 101 | 100 | 100100.00 | 订单 1 详情 |
  8. | 2 | 101 | 100 | 100300.00 | 订单 2 详情 |
  9. +----+------------+-------------+-----------+-----------------+
  10. 4 rows in set (0.00 sec)

全局表

设定为全局的表,会直接复制给每个数据库一份,所有写操作也会同步给多个库。

所以全局表一般不能是大数据表或者更新频繁的表

一般是字典表或者系统表为宜。

测试表-dict_order_type

两台机器都创建这张表

  1. create table dict_order_type(
  2. id INT AUTO_INCREMENT,
  3. order_type varchar(200),
  4. PRIMARY KEY(id)
  5. );

修改 schema.xml

vim /usr/local/mycat/conf/schema.xml

  1. <?xml version="1.0"?>
  2. <!DOCTYPE mycat:schema SYSTEM "schema.dtd">
  3. <mycat:schema xmlns:mycat="http://io.mycat/">
  4. <schema name="TESTDB" checkSQLschema="false" sqlMaxLimit="100" dataNode="dn1">
  5. <table name="customer" dataNode="dn2"></table>
  6. <table name="orders" dataNode="dn1,dn2" rule="mod_rule" >
  7. <childTable name="orders_detail" primaryKey="id" joinKey="order_id" parentKey="id" />
  8. </table>
  9. <table name="dict_order_type" dataNode="dn1,dn2" type="global" ></table>
  10. </schema>
  11. <dataNode name="dn1" dataHost="host1" database="luoma_test_1" />
  12. <dataNode name="dn2" dataHost="host2" database="luoma_test_1" />
  13. <dataHost name="host1" maxCon="1000" minCon="10" balance="0" writeType="0" dbType="mysql" dbDriver="native" switchType="1" slaveThreshold="100">
  14. <heartbeat>select user()</heartbeat>
  15. <writeHost host="host1M1" url="192.168.114.128:3306" user="root" password="123456">
  16. </writeHost>
  17. </dataHost>
  18. <dataHost name="host2" maxCon="1000" minCon="10" balance="0" writeType="0" dbType="mysql" dbDriver="native" switchType="1" slaveThreshold="100">
  19. <heartbeat>select user()</heartbeat>
  20. <writeHost host="host2M1" url="192.168.114.1:3306" user="root" password="123456">
  21. </writeHost>
  22. </dataHost>
  23. </mycat:schema>

重启 Mycat 服务 mycat restart

Mycat 添加数据

mysql -uroot -p123456 -P8066 -h192.168.114.128

  1. insert into dict_order_type(id,order_type) values(101,'type1');
  2. insert into dict_order_type(id,order_type) VALUES(102,'type2');

第一台机器的 dict_order_type 表数据

第二台机器的 dict_order_type 表数据

全局序列

全局序列三种实现方式
1.本地文件方式(不推荐)
2.数据库方式
3.本地时间戳方式(18 位,不推荐)

或者可以自主生成,例如:
1.根据业务逻辑组合
2.可以利用 redis 的单线程原子性 incr 来生成序列

下面介绍使用数据库方式实现全局序列

数据库序列方式原理

利用数据库一个表 来进行计数累加。

但是并不是每次生成序列都读写数据库,这样效率太低

mycat会预加载一部分号段到 mycat 的内存中,这样大部分读写序列都是在内存中完成的。
如果内存中的号段用完了 mycat 会再向数据库要一次。

问:那如果 mycat 崩溃了,那内存中的序列岂不是都没了?
是的。如果是这样,那么 mycat 启动后会向数据库申请新的号段,原有号段会弃用。
也就是说如果 mycat 重启,那么损失是当前的号段没用完的号码,但是不会因此出现主键重复。

建库序列脚本

以下脚本都在第二台机器的 luoma_test_1 数据库创建

1.创建表 MYCAT_SEQUENCE

  1. CREATE TABLE MYCAT_SEQUENCE
  2. (
  3. NAME VARCHAR(50) NOT NULL,
  4. current_value INT NOT NULL,
  5. increment INT NOT NULL DEFAULT 100,
  6. PRIMARY KEY(NAME)
  7. ) ENGINE=INNODB;

2.创建方法 mycat_seq_currval

  1. DELIMITER $$
  2. CREATE FUNCTION mycat_seq_currval(seq_name VARCHAR(50)) RETURNS VARCHAR(64)
  3. DETERMINISTIC
  4. BEGIN
  5. DECLARE retval VARCHAR(64);
  6. SET retval="-999999999,null";
  7. SELECT CONCAT(CAST(current_value AS CHAR),",",CAST(increment AS CHAR)) INTO retval
  8. FROM MYCAT_SEQUENCE WHERE NAME = seq_name;
  9. RETURN retval;
  10. END $$
  11. DELIMITER;

3.创建方法 mycat_seq_setval

  1. DELIMITER $$
  2. CREATE FUNCTION mycat_seq_setval(seq_name VARCHAR(50),VALUE INTEGER) RETURNS VARCHAR(64)
  3. DETERMINISTIC
  4. BEGIN
  5. UPDATE MYCAT_SEQUENCE
  6. SET current_value = VALUE
  7. WHERE NAME = seq_name;
  8. RETURN mycat_seq_currval(seq_name);
  9. END $$
  10. DELIMITER ;

4.创建方法 mycat_seq_nextval

  1. DELIMITER $$
  2. CREATE FUNCTION mycat_seq_nextval(seq_name VARCHAR(50)) RETURNS VARCHAR(64)
  3. DETERMINISTIC
  4. BEGIN
  5. UPDATE MYCAT_SEQUENCE
  6. SET current_value = current_value + increment WHERE NAME = seq_name;
  7. RETURN mycat_seq_currval(seq_name);
  8. END $$
  9. DELIMITER;

5.增加要用的序列

  1. TRUNCATE TABLE MYCAT_SEQUENCE;
  2. ##增加要用的序列
  3. INSERT INTO MYCAT_SEQUENCE(NAME,current_value,increment)
  4. VALUES ('ORDERS', 400000,100);
  5. SELECT * FROM MYCAT_SEQUENCE;

修改 mycat 配置

1.sequence_db_conf.properties

vim /usr/local/mycat/conf/sequence_db_conf.properties

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

意思是 ORDERS 这个序列在 dn1 这个节点上,具体 dn2 节点是哪台机子,参考 schema.xml,这里就是第二台机器

  1. <dataNode name="dn2" dataHost="host2" database="luoma_test_1" />
  2. <dataHost name="host2" maxCon="1000" minCon="10" balance="0" writeType="0" dbType="mysql" dbDriver="native" switchType="1" slaveThreshold="100">
  3. <heartbeat>select user()</heartbeat>
  4. <writeHost host="host2M1" url="192.168.114.1:3306" user="root" password="123456">
  5. </writeHost>
  6. </dataHost>

2.server.xml

vim /usr/local/mycat/conf/server.xml

  1. <property name="sequnceHandlerType">1</property>
sequnceHandlerType 值 描述
0 配置为 0 表示使用本地文件读取
1 配置为 1 表示从数据库表中读取
2 配置为 2 表示时间戳方式

3.重启 Mycat

  1. mycat restart

4.测试前

现在第一台机器的 orders 数据如下

第二台机器的 orders 表数据如下

5.Mycat 添加数据

mysql -uroot -p123456 -P8066 -h192.168.114.128

  1. insert into `orders`(id,amount,customer_id,order_type) values(next value for MYCATSEQ_ORDERS,1000,101,102);
  2. insert into `orders`(id,amount,customer_id,order_type) values(next value for MYCATSEQ_ORDERS,1000,100,102);

5.测试后

现在第一台机器的 orders 数据如下

第二台机器的 orders 表数据如下