先发制人:分库分表参考: 分库分表面试参考
1,分片概述
连续
范围分片
日期/时间分片
优点:
1)范围条件查询消耗资源少(不需要汇总数据)
2)扩容无需迁移数据(分片固定)
缺点:
1)存在数据热点的可能性
2)并发访问能力受限于单一或少量DataNode(访问集中)
离散
取模
枚举
一致性哈希 (比如把分片键的值进行哈希,然后根据哈希值进行算法计算)
固定分片哈希
取模范围
范围取模
优点:
1)并发访问能力增强(负载到不同的节点)
2)范围条件查询性能提升(并行计算) [性能提升仅仅是多个节点都查询了一部分数据]
缺点:
1)数据扩容比较困难,涉及到数据迁移问题
2)数据库连接消耗比较多
2,分片测试
2.1 测试 全局表
在Mycat连接中gupao数据库中执行
--测试全局表
INSERT INTO `dict` (`id`, `param_code`, `param_name`) VALUES (1, '0731', '长沙市');
然后三张表都有了数据
查询呢?原来会随机路由到三个数据节点中某一个
EXPLAIN SELECT * from dict;
2.2 枚举分片
在122,123,124数据节点中创建表:
CREATE TABLE `sharding_by_intfile` (
`age` int(11) NOT NULL,
`db_nm` varchar(20) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
在schema.xml中配置:
<table name="sharding_by_intfile" primaryKey="age" dataNode="122-imall,123-imall,124-imall" rule="qs-sharding-by-intfile" />
在rule.xml中配置:
<tableRule name="qs-sharding-by-intfile">
<rule>
<columns>age</columns>
<algorithm>hash-int</algorithm>
</rule>
</tableRule>
更改partition-hash-int.txt文件内容如下:
16=0
17=1
18=2
然后在Mycat服务节点上执行下面sql:
INSERT INTO `sharding_by_intfile` (age,db_nm) VALUES (16, database());
INSERT INTO `sharding_by_intfile` (age,db_nm) VALUES (17, database());
INSERT INTO `sharding_by_intfile` (age,db_nm) VALUES (18, database());
结果:
2.3 范围分片
之前schema.xml中配置:
<table name="customer" primaryKey="id" dataNode="122-imall,123-imall,124-imall" rule="auto-sharding-long" />
修改算法中的文件autopartition-long.txt
原来:
0-500M=0
500M-1000M=1
1000M-1500M=2
改成:
0-10000=0
10001-20000=1
20001-30000=2
原理:在Mycat上执行的时候,执行的sql语句会根据schema.xml配置的数据节点以及rule.xml中配置的算法找到真正要落地的数据节点,
然后把相应的sql路由到相应的数据节点上进行执行。
在Mycat服务中执行下面sql:
INSERT INTO `customer` (`id`, `name`) VALUES (6666, '赵先生');
INSERT INTO `customer` (`id`, `name`) VALUES (7777, '钱先生');
INSERT INTO `customer` (`id`, `name`) VALUES (16666, '孙先生');
INSERT INTO `customer` (`id`, `name`) VALUES (17777, '李先生');
INSERT INTO `customer` (`id`, `name`) VALUES (26666, '周先生');
INSERT INTO `customer` (`id`, `name`) VALUES (27777, '吴先生');
在Mycat服务上查询
2.4 取模分片
-- rule.xml中
<function name="mod-long" class="io.mycat.route.function.PartitionByMod">
<!-- 有几个节点,这里count就要配置几 -->
<property name="count">3</property>
</function>
--测试取模分片(在mycat连接中gupao数据库中执行)
INSERT INTO `student` (`sid`, `name`, `qq`) VALUES (1, '李大彪', '166669999');
INSERT INTO `student` (`sid`, `name`, `qq`) VALUES (4, '菜狗子', '655556666');
INSERT INTO `student` (`sid`, `name`, `qq`) VALUES (2, '等候那場雪', '466669999');
INSERT INTO `student` (`sid`, `name`, `qq`) VALUES (5, '猫老公', '265286999');
INSERT INTO `student` (`sid`, `name`, `qq`) VALUES (3, 'tj-大白', '368828888');
INSERT INTO `student` (`sid`, `name`, `qq`) VALUES (6, '大郎', '516895555');
但是报错:
Error : bad insert sql (sharding column:ID not provided,INSERT INTO `student` (`sid`, `name`, `qq`)
原来是rule.xml中 mod-long算法没改
-- 原来的
<tableRule name="mod-long">
<rule>
<columns>id</columns>
<algorithm>mod-long</algorithm>
</rule>
</tableRule>
-- 改成
<tableRule name="mod-long">
<rule>
<columns>sid</columns>
<algorithm>mod-long</algorithm>
</rule>
</tableRule>
然后重启mMycat服务,执行sql文件成功!
2.5 一致性哈希分片
在122,123,124数据节点中创建表:
CREATE TABLE `sharding_by_intfile` (
`age` int(11) NOT NULL,
`db_nm` varchar(20) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
在schema.xml中配置:
<table name="sharding_by_murmur" primaryKey="id" dataNode="122-imall,123-imall,124-imall" rule="qs-sharding-by-murmur" />
在rule.xml中新增:
<function name="qs-murmur" class="io.mycat.route.function.PartitionByMurmurHash">
<property name="seed">0</property><!-- 默认是0 -->
<property name="count">3</property><!-- 要分片的数据库节点数量,必须指定,否则没法分片 -->
<property name="virtualBucketTimes">160</property>
</function>
然后在Mycat服务节点上执行下面sql:
INSERT INTO `sharding_by_murmur` (id,db_nm) VALUES (1, database());
INSERT INTO `sharding_by_murmur` (id,db_nm) VALUES (2, database());
INSERT INTO `sharding_by_murmur` (id,db_nm) VALUES (3, database());
INSERT INTO `sharding_by_murmur` (id,db_nm) VALUES (4, database());
INSERT INTO `sharding_by_murmur` (id,db_nm) VALUES (5, database());
INSERT INTO `sharding_by_murmur` (id,db_nm) VALUES (6, database());
INSERT INTO `sharding_by_murmur` (id,db_nm) VALUES (7, database());
INSERT INTO `sharding_by_murmur` (id,db_nm) VALUES (8, database());
INSERT INTO `sharding_by_murmur` (id,db_nm) VALUES (9, database());
INSERT INTO `sharding_by_murmur` (id,db_nm) VALUES (10, database());
INSERT INTO `sharding_by_murmur` (id,db_nm) VALUES (11, database());
INSERT INTO `sharding_by_murmur` (id,db_nm) VALUES (12, database());
INSERT INTO `sharding_by_murmur` (id,db_nm) VALUES (13, database());
INSERT INTO `sharding_by_murmur` (id,db_nm) VALUES (14, database());
INSERT INTO `sharding_by_murmur` (id,db_nm) VALUES (15, database());
INSERT INTO `sharding_by_murmur` (id,db_nm) VALUES (16, database());
INSERT INTO `sharding_by_murmur` (id,db_nm) VALUES (17, database());
INSERT INTO `sharding_by_murmur` (id,db_nm) VALUES (18, database());
INSERT INTO `sharding_by_murmur` (id,db_nm) VALUES (19, database());
INSERT INTO `sharding_by_murmur` (id,db_nm) VALUES (20, database());
查询:
2.6 固定分片哈希
在122,123,124数据节点中创建表:
CREATE TABLE `sharding_by_long` (
`id` int(10) DEFAULT NULL,
`db_nm` varchar(20) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
在schema.xml中配置:
<table name="sharding_by_long" dataNode="122-imall,123-imall,124-imall" rule="qs-sharding-by-long" />
在rule.xml中配置:
<tableRule name="qs-sharding-by-long">
<rule>
<columns>id</columns>
<algorithm>qs-sharding-by-long</algorithm>
</rule>
</tableRule>
<function name="qs-sharding-by-long" class="io.mycat.route.function.PartitionByLong">
<!-- partitionCount的数量必须要和partitionLength的数量一样,比如都是两个 -->
<property name="partitionCount">2,1</property>
<property name="partitionLength">256,512</property>
</function>
然后在Mycat服务节点上执行下面sql:
INSERT INTO `sharding_by_long` (id,db_nm) VALUES (222, database());
INSERT INTO `sharding_by_long` (id,db_nm) VALUES (333, database());
INSERT INTO `sharding_by_long` (id,db_nm) VALUES (666, database());
<property name="partitionCount">2,1</property>
<property name="partitionLength">256,512</property>
详解:partitionCount的数字加一起等于节点数,2+1=3; partitionCount和partitionLength的个数必须一致。如下图,partitionCount表示分成了两组,第一组是2,表示256有2个{[0-254],[255-511]},第二组是1,表示512有1个{[512-1023]}
查询:
2.7 取模范围分片 (注:连续分片和离散分片的组合)
取模范围:先取模再范围,比如先模100后再范围
在122,123,124数据节点中创建表:
CREATE TABLE `sharding_by_pattern` (
`id` varchar(20) DEFAULT NULL,
`db_nm` varchar(20) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
在schema.xml中新增配置:
<table name="sharding_by_pattern" primaryKey="id" dataNode="122-imall,123-imall,124-imall" rule="sharding-by-pattern" />
在rule.xml中新增配置:
<tableRule name="sharding-by-pattern">
<rule>
<columns>id</columns>
<algorithm>sharding-by-pattern</algorithm>
</rule>
</tableRule>
<function name="sharding-by-pattern" class="io.mycat.route.function.PartitionByPattern">
//先取模再范围,比如先模100后再范围(1-20=0,21-70=1,71-100=2)
<property name="patternValue">100</property>
<property name="defaultNode">0</property>
<property name="mapFile">partition-pattern.txt</property>
</function>
//新增partition-pattern.txt文件
1-20=0
21-70=1
71-100=2
然后在Mycat服务节点上执行下面sql:
INSERT INTO `sharding_by_pattern` (id,db_nm) VALUES (19, database());
INSERT INTO `sharding_by_pattern` (id,db_nm) VALUES (222, database());
INSERT INTO `sharding_by_pattern` (id,db_nm) VALUES (371, database());
查询:
2.8 范围取模 (先范围后取模)
取模范围:先取模再范围,比如先模100后再范围
在122,123,124数据节点中创建表:
CREATE TABLE `sharding_by_rang_mod` (
`id` bigint(20) DEFAULT NULL,
`db_nm` varchar(20) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
在schema.xml中新增配置:
<table name="sharding_by_range_mod" dataNode="122-imall,123-imall,124-imall" rule="qs-sharding-by-range-mod"/>
在rule.xml中新增配置:
<tableRule name="qs-sharding-by-range-mod">
<rule>
<columns>id</columns>
<algorithm>qs-range-mod</algorithm>
</rule>
</tableRule>
<function name="qs-range-mod" class="io.mycat.route.function.PartitionByRangeMod">
<property name="mapFile">partition-range-mod.txt</property>
</function>
//修改partition-range-mod.txt文件
0-20000=1 //id在0-20000只能路由在122节点上
20001-40000=2 //id在20001-40000之间的模2余0落在123节点,模2余1落在124节点
在Mycat服务上新增数据:
INSERT INTO `sharding_by_rang_mod` (id,db_nm) VALUES (666, database());
INSERT INTO `sharding_by_rang_mod` (id,db_nm) VALUES (6666, database());
INSERT INTO `sharding_by_rang_mod` (id,db_nm) VALUES (16666, database());
INSERT INTO `sharding_by_rang_mod` (id,db_nm) VALUES (21111, database());
INSERT INTO `sharding_by_rang_mod` (id,db_nm) VALUES (22222, database());
INSERT INTO `sharding_by_rang_mod` (id,db_nm) VALUES (23333, database());
INSERT INTO `sharding_by_rang_mod` (id,db_nm) VALUES (24444, database());
查询:
2.9 单库分表
1),之前schema.xml中的的配置:
<!-- 单库分表,只在一个节点上根据月份分成三个表 -->
<table name="fee" primaryKey="id" subTables="fee2025$1-3" dataNode="122-gupao" rule="sharding-by-month" />
2),修改rule.xml文件
<function name="partbymonth" class="io.mycat.route.function.PartitionByMonth">
<property name="dateFormat">yyyy-MM-dd</property>
<property name="sBeginDate">2015-01-01</property>
</function>
改为:
<function name="partbymonth" class="io.mycat.route.function.PartitionByMonth">
<property name="dateFormat">yyyy-MM-dd</property>
<property name="sBeginDate">2025-01-01</property>
</function>
-- 在第一个数据库节点(122)gupao数据库创建单库分片表,创建好之后在Mycat服务中都有这些表了
CREATE TABLE `fee` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`create_time` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
CREATE TABLE `fee20251` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`create_time` datetime(0) NULL DEFAULT NULL,
PRIMARY KEY (`id`)
);
CREATE TABLE `fee20252` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`create_time` datetime(0) NULL DEFAULT NULL,
PRIMARY KEY (`id`)
);
CREATE TABLE `fee20253` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`create_time` datetime(0) NULL DEFAULT NULL,
PRIMARY KEY (`id`)
);
然后在Mycat服务连接中gupao数据库中执行
INSERT INTO `fee` (`id`, `create_time`) VALUES (1, '2025-1-1 14:46:19');
INSERT INTO `fee` (`id`, `create_time`) VALUES (2, '2025-2-1 14:46:19');
INSERT INTO `fee` (`id`, `create_time`) VALUES (3, '2025-3-1 14:46:19');
然后可以看到数据节点122上面
2.10 ER分片
<table name="order_info" dataNode="122-imall,123-imall,124-imall" rule="mod-long-order">
<childTable name="order_detail" primaryKey="id" joinKey="order_id" parentKey="order_id"> </childTable>
</table>
-- 测试ER分片(在Mycat连接中imall数据库中执行)
INSERT INTO `order_info` (`order_id`, `uid`, `nums`, `state`, `create_time`, `update_time`) VALUES (1, 1000001, 1, 2, '2025-9-23 14:35:37', '2025-9-23 14:35:37');
INSERT INTO `order_info` (`order_id`, `uid`, `nums`, `state`, `create_time`, `update_time`) VALUES (2, 1000002, 1, 2, '2025-9-24 14:35:37', '2025-9-24 14:35:37');
INSERT INTO `order_info` (`order_id`, `uid`, `nums`, `state`, `create_time`, `update_time`) VALUES (3, 1000003, 3, 1, '2025-9-25 11:35:49', '2025-9-25 11:35:49');
INSERT INTO `order_detail` (`order_id`, `id`, `goods_id`, `price`, `is_pay`, `is_ship`, `status`) VALUES (3, 20180001, 85114752, 19.99, 1, 1, 1);
INSERT INTO `order_detail` (`order_id`, `id`, `goods_id`, `price`, `is_pay`, `is_ship`, `status`) VALUES (1, 20180002, 25411251, 1280.00, 1, 1, 0);
INSERT INTO `order_detail` (`order_id`, `id`, `goods_id`, `price`, `is_pay`, `is_ship`, `status`) VALUES (1, 20180003, 62145412, 288.00, 1, 1, 2);
INSERT INTO `order_detail` (`order_id`, `id`, `goods_id`, `price`, `is_pay`, `is_ship`, `status`) VALUES (2, 20180004, 21456985, 399.00, 1, 1, 2);
INSERT INTO `order_detail` (`order_id`, `id`, `goods_id`, `price`, `is_pay`, `is_ship`, `status`) VALUES (2, 20180005, 21457452, 1680.00, 1, 1, 2);
INSERT INTO `order_detail` (`order_id`, `id`, `goods_id`, `price`, `is_pay`, `is_ship`, `status`) VALUES (2, 20180006, 65214789, 9999.00, 1, 1, 3);
-- 122
-- 123
-- 124
2.11 非分片表
之前schema.xml中配置:
<table name="noshard" primaryKey="id" autoIncrement="true" dataNode="122-gupao" />
-- 在第一个数据库节点(122)gupao数据库创建非分片表
CREATE TABLE `noshard` (
`id` bigint(30) DEFAULT NULL,
`name` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
truncate table noshard;
-- 测试非分片表(在Mycat连接中gupao数据库中执行)
INSERT INTO `noshard` (`id`, `name`) VALUES (1, '这是一条没有分片的数据');
查询的时候就只会把sql路由到固定的数据节点上进行查询:
3,全局ID
1,文件方式(0)
2,数据库方式(1)
3,时间戳方式(2)
4,ZK方式(3)
<!-- 0文件方式,1数据库方式,3时间戳方式,4ZK方式 -->
<property name="sequenceHandlerType">2</property>
3.1 文件方式
<property name="sequenceHandlerType">0</property>
select 'next value for MYCATSEQ_CUSTOMER';
3.2 数据库方式
server.xml中配置:
<property name="sequenceHandlerType">1</property>
schema.xml中配置:
<table name="mycat_sequence" dataNode="122-imall" autoIncrement="true" primaryKey="id"></table>
//新建一张表和三个函数
-- 在第一个数据库节点上(122)imall数据库创建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 1,
remark varchar(100),
PRIMARY KEY(name)) ENGINE=InnoDB;
-- 122-imall创建函数——获取当前sequence的值
DROP FUNCTION IF EXISTS `mycat_seq_currval`;
DELIMITER ;;
CREATE DEFINER=`heymen_127`@`%` FUNCTION `mycat_seq_currval`(seq_name VARCHAR(50)) RETURNS varchar(64) CHARSET latin1
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 ;
-- 122-imall创建函数,获取下一个sequence
DROP FUNCTION IF EXISTS `mycat_seq_nextval`;
DELIMITER ;;
CREATE DEFINER=`heymen_127`@`%` FUNCTION `mycat_seq_nextval`(seq_name VARCHAR(50)) RETURNS varchar(64) CHARSET latin1
DETERMINISTIC
BEGIN
UPDATE MYCAT_SEQUENCE
SET current_value = current_value + increment WHERE name = seq_name;
RETURN mycat_seq_currval(seq_name);
END
;;
DELIMITER ;
-- 122-imall创建函数,设置sequence
DROP FUNCTION IF EXISTS `mycat_seq_setval`;
DELIMITER ;;
CREATE DEFINER=`heymen_127`@`%` FUNCTION `mycat_seq_setval`(seq_name VARCHAR(50), value INTEGER) RETURNS varchar(64) CHARSET latin1
DETERMINISTIC
BEGIN
UPDATE MYCAT_SEQUENCE
SET current_value = value
WHERE name = seq_name;
RETURN mycat_seq_currval(seq_name);
END
;;
DELIMITER ;
--122-imall数据节点插入记录
INSERT INTO MYCAT_SEQUENCE(name,current_value,increment,remark) VALUES ('GLOBAL', 1, 100,'');
INSERT INTO MYCAT_SEQUENCE(name,current_value,increment,remark) VALUES ('ORDERS', 1, 100,'订单表使用');
修改sequence_db_conf.properties文件
由
GLOBAL=dn1
COMPANY=dn1
CUSTOMER=dn1
ORDERS=dn1
改为:
GLOBAL=122-imall
COMPANY=122-imall
CUSTOMER=122-imal
ORDERS=122-imall
然后Mycat服务节点上执行
select 'next value for MYCATSEQ_ORDERS';
3.3 时间戳方式
在server.xml中配置:
<property name="sequenceHandlerType">2</property>
其实是使用了sequence_time_conf.properties文件,此文件不需修改
//其实就是雪花算法,【雪花算法就是基于时间戳的】
#sequence depend on TIME
WORKID=01
DATAACENTERID=01
在Mycat服务节点运行:
select 'next value for MYCATSEQ_GLOBAL';
每次执行生成的都不一样:
那么每次insert的时候必须要把 select 'next value for MYCATSEQ_GLOBAL' 写在指定的ID的位置上呢?没有必要,可以在schema.xml中配置一个字段:拿不分库的noshard来来说,配置一下:
<!-- 配置autoIncrement="true" 然后在新增记录的时候不设置id字段,则会用mycat的自增ID -->
<table name="noshard" primaryKey="id" autoIncrement="true" dataNode="122-gupao" />
测试
insert into `noshard` (`name`) VALUES ('这里是测试mycat全局自增ID的');
4,使用问题
4.1 Mycat连接MySQL8.0遇到的问题
把Mycat的lib目录下的MySQL驱动包升级到8.0 比如:mysql-connector-java-8.0.16.jar