一、简介
在MySQL中,分库分表是一种常用的数据库优化策略,特别是在数据量巨大时,可以有效提高查询性能和系统的可扩展性。以下简单介绍下分库分表的概念:
- 分库:将数据分散到多个数据库中,每个数据库可以放在不同的物理服务器上。
- 分表:将一个大表拆分成多个小表,这些小表可以在同一个数据库中,也可以分布在不同的数据库中。
二、分库分表的策略
分库分表的策略主要有以下两种:
-
垂直分库分表:
- 垂直分库:根据业务模块将不同的表分配到不同的数据库中。例如,将用户信息放在一个数据库,将订单信息放在另一个数据库。
- 垂直分表:将一个表的列拆分成多个表。例如,将用户表拆分为用户基本信息表和用户扩展信息表。
优点:
- 结构清晰:垂直分库分表是根据业务模块或功能将数据拆分到不同的数据库中,结构更加清晰,便于管理。
- 易于维护:不同的业务模块可以独立进行数据库优化和维护,减少了相互之间的影响。
- 安全性高:不同模块的数据分开存储,可以提高数据的安全性和隔离性。
- 适合复杂业务:对于业务逻辑复杂且模块化明显的系统,垂直分库分表可以有效地组织数据。
缺点:
- 跨库查询复杂:由于数据分布在不同的数据库中,涉及多个模块的数据查询会变得复杂,可能需要分布式事务支持。
- 扩展性有限:垂直分库分表主要解决的是业务模块的隔离问题,对于单个模块内的数据量增长,扩展性有限。
- 开发成本高:需要对系统进行详细的业务分析和设计,开发和维护成本较高。
-
水平分库分表:
- 水平分库:将同一个表的数据按某种规则分配到不同的数据库中。
- 水平分表:将同一个表的数据按某种规则分配到多个表中。
优点:
- 高扩展性:通过将同一张表的数据分布到多个数据库中,可以轻松应对数据量的增长,扩展性强。
- 负载均衡:数据分布在多个数据库中,可以有效地分散读写压力,提高系统的整体性能。
- 单库压力小:每个数据库只存储部分数据,单个数据库的压力较小,性能更好。
缺点:
- 分片策略复杂:需要设计合理的分片策略,以确保数据的均匀分布和查询效率。
- 事务处理复杂:跨分片的事务处理复杂,可能需要引入分布式事务管理。
- 数据一致性问题:在分布式环境下,数据的一致性维护变得更加复杂。
- 开发难度大:需要对系统进行详细的设计和实现,开发和维护成本较高。
三、分库分表常用的中间件
虽然分库分表的策略已经很明确,但是要如何才能在开发中实现分库分表的效果呢?目前已经有很多成熟的中间件,只需要根据业务需求选择合适的中间件引入到项目中,就可以轻松的实现分库分表的目的。以下是现在比较常用的中间件:
- MyCat:
- MyCat是一个开源的数据库中间件,支持MySQL的分库分表功能。它可以将SQL请求路由到不同的数据库实例中,并支持读写分离、分片、分布式事务等功能。
- ShardingSphere:
- Apache ShardingSphere是一个开源的分布式数据库中间件解决方案,支持分库分表、读写分离、数据加密等功能。它提供了JDBC、Proxy和Sidecar三种模式,适用于不同的应用场景。
- Cobar:
- Cobar是阿里巴巴开源的一个分布式数据库中间件,主要用于MySQL的分库分表。它支持SQL解析、路由、执行等功能,适合大规模数据的分布式处理。
- Vitess:
- Vitess是一个开源的数据库集群系统,最初由YouTube开发。它可以将MySQL水平扩展到数千个节点,支持分库分表、自动分片、负载均衡等功能。
- Atlas:
- Atlas是由Qihoo 360开发的一个MySQL协议的中间层代理,支持读写分离、分库分表等功能。它可以作为MySQL的前端代理,处理SQL请求的路由和负载均衡。
四、SpringBoot整合MyCat实现分库分表
在Spring Boot项目中整合MyCAT实现分库分表是一个复杂的过程,涉及到数据库配置、MyCAT配置以及Spring Boot的相关设置。
4.1、准备工作
- 安装MyCAT:确保你已经安装并配置好MyCAT。
- 数据库准备:准备好需要分库分表的数据库实例。
- Spring Boot 项目:创建一个 Spring Boot 项目。
4.2、配置MyCAT
Mycat中,实现分库分表需要配置三个主要的XML文件:server.xml、schema.xml和rule.xml。下面是一个简单的配置示例:
(1)在server.xml配置Mycat服务器的基本信息,包括用户认证、端口等。
<server>
<system>
<property name="serverPort">8066</property>
<property name="managerPort">9066</property>
</system>
<user name="root">
<property name="password">root</property>
</user>
<user name="test">
<property name="password">test</property>
</user>
</server>
(2)在schema.xml文件定义逻辑库和物理库的映射关系。
<mycat:schema xmlns:mycat="http://io.mycat/">
<!-- 逻辑库配置 -->
<schema name="TESTDB" checkSQLschema="true" sqlMaxLimit="100">
<!-- 逻辑表配置 -->
<table name="user" dataNode="dn1,dn2" rule="mod-long" />
</schema>
<!-- 数据节点配置 -->
<dataNode name="dn1" dataHost="localhost1" database="db1" />
<dataNode name="dn2" dataHost="localhost2" database="db2" />
<!-- 数据主机配置 -->
<dataHost name="localhost1" maxCon="1000" minCon="10" balance="0" writeType="0" dbType="mysql" dbDriver="native" switchType="1" slaveThreshold="100">
<heartbeat>select user()</heartbeat>
<writeHost host="hostM1" url="192.168.1.101:3306" user="root" password="123456" />
</dataHost>
<dataHost name="localhost2" maxCon="1000" minCon="10" balance="0" writeType="0" dbType="mysql" dbDriver="native" switchType="1" slaveThreshold="100">
<heartbeat>select user()</heartbeat>
<writeHost host="hostM2" url="192.168.1.102:3306" user="root" password="123456" />
</dataHost>
</mycat:schema>
(3)在rule.xml文件用于定义分片规则。
<mycat:rule xmlns:mycat="http://io.mycat/">
<!-- 取模分片规则 -->
<tableRule name="mod-long">
<rule>
<columns>user_id</columns>
<algorithm>mod-long</algorithm>
</rule>
</tableRule>
<!-- 取模分片算法 -->
<function name="mod-long" class="io.mycat.route.function.PartitionByMod">
<property name="count">2</property>
</function>
</mycat:rule>
4.3、配置Spring Boot
-
添加依赖
在 pom.xml 中添加 MyBatis 和 MySQL 驱动依赖:<dependencies> <!-- Spring Boot Starter --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- MyBatis --> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.2.0</version> </dependency> <!-- MySQL 驱动 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.26</version> </dependency> <!-- Lombok --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <scope>provided</scope> </dependency> </dependencies>
-
配置数据源
在Spring Boot中配置数据源,指向Mycat的地址和端口
。常用的方式有以下两种:-
在src/main/resources目录下的application.properties中配置数据源。
spring.datasource.url=jdbc:mysql://mycat-server:8066/testDB spring.datasource.username=root spring.datasource.password=123456 spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
-
通过Java配置类来配置数据源,这种方式可以让你在代码中更灵活地控制数据源的配置。
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import javax.sql.DataSource; import org.springframework.boot.jdbc.DataSourceBuilder; @Configuration public class DataSourceConfig { @Bean public DataSource dataSource() { return DataSourceBuilder.create() .url("jdbc:mysql://mycat-server:8066/testDB") .username("root") .password("123456") .driverClassName("com.mysql.cj.jdbc.Driver") .build(); } }
-
4.4、启动测试
- 启动MyCAT服务。
- 启动Spring Boot应用。
- 测试数据库的分库分表功能,确保数据能够正确地路由到不同的数据库实例。
五、配置文件解析
-
server.xml
在MyCAT的conf目录下,找到server.xml文件,主要配置 MyCAT 服务器的全局参数,如系统属性、用户权限、防火墙规则等。<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mycat:server SYSTEM "server.dtd"> <mycat:server xmlns:mycat="http://io.mycat/"> <!-- Mycat 系统属性配置 --> <system> <!-- MyCAT 管理端口号,默认 9066 --> <property name="managerPort">9066</property> <!-- Mycat 服务器的监听端口 --> <property name="serverPort">8066</property> <!-- Mycat 服务器的字符集 --> <property name="charset">utf8</property> <!-- 处理线程数,默认值为 CPU 核心数 --> <property name="processors">4</property> <!-- 线程池大小,默认值为 32 --> <property name="processorExecutor">32</property> <!-- 全局序列生成方式,默认为 0 --> <property name="sequnceHandlerType">0</property> </system> <!-- 用户权限配置 --> <user name="root"> <!-- 用户密码 --> <property name="password">root</property> <!-- 用户可以访问的逻辑库(多个库用逗号分隔) --> <property name="schemas">schema1,schema2</property> </user> <!-- 防火墙配置 --> <firewall> <!-- 白名单 --> <whitehost> <host host="127.0.0.1" user="root"/> <host host="192.168.1.*" user="user"/> </whitehost> <!-- 黑名单 --> <blacklist check="true"> <host host="192.168.1.100"/> </blacklist> <!-- SQL 防火墙 --> <sqlfirewall> <rule pattern="DROP TABLE" action="deny"/> <rule pattern="DELETE FROM" action="allow"/> </sqlfirewall> </firewall>
-
schema.xml
schema.xml 是 MyCAT 的核心配置文件之一,用于定义逻辑库、逻辑表、数据节点、分片规则等。以下是对 schema.xml 的详细配置说明和示例。<mycat:schema xmlns:mycat="http://io.mycat/"> <!-- 逻辑库配置 --> <schema name="TESTDB" checkSQLschema="true" sqlMaxLimit="100"> <!-- 单表配置 --> <table name="user" dataNode="dn1" /> <!-- 分片表配置 --> <table name="order" dataNode="dn1,dn2" rule="mod-long" /> </schema> <!-- 数据节点配置 --> <dataNode name="dn1" dataHost="localhost1" database="db1" /> <dataNode name="dn2" dataHost="localhost2" database="db2" /> <!-- 数据主机配置 --> <dataHost name="localhost1" maxCon="1000" minCon="10" balance="0" writeType="0" dbType="mysql" dbDriver="native" switchType="1" slaveThreshold="100"> <heartbeat>select user()</heartbeat> <writeHost host="hostM1" url="192.168.1.101:3306" user="root" password="123456"> <readHost host="hostS1" url="192.168.1.102:3306" user="root" password="123456" /> </writeHost> </dataHost> <dataHost name="localhost2" maxCon="1000" minCon="10" balance="0" writeType="0" dbType="mysql" dbDriver="native" switchType="1" slaveThreshold="100"> <heartbeat>select user()</heartbeat> <writeHost host="hostM2" url="192.168.1.103:3306" user="root" password="123456"> <readHost host="hostS2" url="192.168.1.104:3306" user="root" password="123456" /> </writeHost> </dataHost> </mycat:schema> 详细配置说明 schema: 定义逻辑库。 name: 逻辑库的名称。 checkSQLschema: 是否检查 SQL 中的逻辑库名,默认为 false。 sqlMaxLimit: SQL 查询的最大返回行数。 table: 定义逻辑表。 name: 表名。 primaryKey: 主键字段。 dataNode: 数据节点名称。 rule: 分片规则名称。 dataNode: 定义数据节点。 name: 数据节点的名称。 dataHost: 数据主机名称。 database: 物理数据库名称,这个数据库名是在目标数据库服务器上已经存在的数据库。 dataHost: 定义数据主机。 name: 数据主机的名称。 maxCon: 最大连接数。 minCon: 最小连接数。 balance: 负载均衡策略。0:不开启读写分离;1:读操作随机分配到读节点;2:读操作随机分配到写节点和读节点;3:读操作分配到读节点。 writeType: 写入类型。0:写操作分配到写节点;1:写操作随机分配到写节点。 dbType: 数据库类型(如 mysql、oracle)。 dbDriver: 数据库驱动。 switchType: 切换类型。-1:不自动切换;1:自动切换;2:基于 MySQL 主从状态切换。 slaveThreshold: 主从延迟阈值(单位:秒)。 heartbeat: 心跳检测 SQL。 writeHost/readHost: 定义主从数据库连接。 host: 主机地址。 url: 数据库连接 URL。 user: 数据库用户名。 password: 数据库密码。
-
rule.xml
rule.xml 是 MyCAT 的分片规则配置文件,用于定义数据分片的规则。通过 rule.xml,可以指定如何将数据分布到不同的数据节点上。以下是 rule.xml 的详细配置说明和示例。<mycat:rule xmlns:mycat="http://io.mycat/"> <!-- 取模分片规则 --> <tableRule name="mod-long"> <rule> <columns>user_id</columns> <algorithm>mod-long</algorithm> </rule> </tableRule> <!-- 范围分片规则 --> <tableRule name="auto-sharding-long"> <rule> <columns>order_id</columns> <algorithm>auto-sharding-long</algorithm> </rule> </tableRule> <!-- 取模分片算法定义 --> <function name="mod-long" class="io.mycat.route.function.PartitionByMod"> <property name="count">2</property> </function> <!-- 范围分片算法定义 --> <function name="auto-sharding-long" class="io.mycat.route.function.AutoPartitionByLong"> <property name="mapFile">autopartition-long.txt</property> </function> </mycat:rule>
六、常用分片算法
MyCAT 提供了多种内置分片算法,以下是常见的几种:
-
范围分片(PartitionByRange)
- 原理:根据分片字段的值范围,将数据分布到不同的数据节点。
- 算法类:io.mycat.route.function.PartitionByRange。
- 适用场景:分片字段是整数类型,且数据有明显的范围特征。
- 配置参数:
- mapFile:范围映射文件的路径,文件中定义了分片字段的范围与数据节点的映射关系。
- ranges:直接在 rule.xml 中定义范围规则,格式为 起始值-结束值=数据节点编号。
- defaultNode:默认数据节点,当分片字段的值不在映射范围内时,数据会路由到该节点。
<!-- 通过 mapFile 配置 --> <function name="range-sharding" class="io.mycat.route.function.PartitionByRange"> <property name="mapFile">partition-range.txt</property> <property name="defaultNode">0</property> </function> <!-- 通过 ranges 配置 --> <function name="range-sharding" class="io.mycat.route.function.PartitionByRange"> <property name="ranges"> 0-10000=0, 10001-20000=1, 20001-30000=2 </property> <property name="defaultNode">0</property> </function> 注意: 1.partition-range.txt是一个文本文件,定义了具体的分片范围, 每一行定义一个分片,格式为分片编号=起始值-结束值。文件内容可能如下: 0=0-10000 1=10001-20000 2=20001-30000 2.确保partition-range.txt文件路径正确,并且MyCAT能够访问到该文件。
-
哈希分片(PartitionByHashMod)
- 原理:根据分片字段的哈希值取模,将数据分布到不同的数据节点。
- 算法类:io.mycat.route.function.PartitionByHashMod。
- 适用场景:分片字段是字符串或整数类型,且数据分布均匀。
- 配置参数:
- count:分片数量(即数据节点数量)。
<function name="hash-int" class="io.mycat.route.function.PartitionByHashMod"> <property name="count">3</property> </function> 注意: 1.确保count的值与实际数据库中分片的数量一致。 2.user_id列应该是一个适合哈希运算的字段,通常是主键或唯一标识符。 3.根据实际需求,可能需要调整分片算法和分片数量。
-
枚举分片(PartitionByFileMap)
- 原理:根据分片字段的枚举值,将数据分布到不同的数据节点。
- 算法类:io.mycat.route.function.PartitionByFileMap。
- 适用场景:分片字段是枚举类型,且枚举值与数据节点有明确的映射关系。
- 配置参数:
- mapFile:枚举映射文件路径,定义枚举值与数据节点的映射关系。
- defaultNode:默认数据节点,当分片字段值不在枚举范围内时使用。
<function name="enum-sharding" class="io.mycat.route.function.PartitionByFileMap"> <property name="mapFile">partition-enum.txt</property> <property name="defaultNode">0</property> </function> 注意: 1.假设我们有三个状态:NEW、PROCESSING、COMPLETED,并且我们希望将它们映射到不同的分片: NEW=0 PROCESSING=1 COMPLETED=2 在这个映射文件中,NEW 状态的数据将被路由到分片 0, PROCESSING 状态的数据将被路由到分片 1, COMPLETED 状态的数据将被路由到分片 2。 2.文件路径:确保 partition-enum.txt 文件的路径正确,并且 MyCAT 能够访问到该文件。 3.分片数量:确保分片数量与实际的数据库分片配置一致。
-
取模分片(mod-long):
- 原理:根据分片字段的值取模,将数据分布到不同的数据节点。
- 实现类:io.mycat.route.function.PartitionByMod。
- 适用场景:分片字段是整数类型,且数据分布均匀。
- 配置参数:
- count:分片数量。
<function name="mod-long" class="io.mycat.route.function.PartitionByMod"> <property name="count">2</property> </function>
-
日期分片(PartitionByDate)
- 原理:根据分片字段的日期值,将数据分布到不同的数据节点。
- 算法类:io.mycat.route.function.PartitionByDate。
- 适用场景:分片字段是日期类型,且数据按时间分布。
- 配置参数:
- dateFormat:日期格式(如 yyyy-MM-dd)。
- sBeginDate:开始日期。
- sEndDate:结束日期。
- sPartionDay:分片间隔天数。
<function name="sharding-by-date" class="io.mycat.route.function.PartitionByDate"> <property name="dateFormat">yyyy-MM-dd</property> <property name="sBeginDate">2023-01-01</property> <property name="sEndDate">2023-12-31</property> <property name="sPartionDay">30</property> </function> 注意: 1.日期格式:确保dateFormat与数据库中日期字段的格式一致。 2.起始日期:sBeginDate应根据实际数据的最早日期设置。
-
一致性哈希分片(PartitionByMurmurHash)
- 原理:使用一致性哈希算法,将数据分布到不同的数据节点。
- 算法类:io.mycat.route.function.PartitionByMurmurHash。
- 适用场景:分片字段是字符串或整数类型,且需要支持动态扩容。
- 配置参数:
- seed:哈希种子,默认为 0。
- count:分片数量(即数据节点数量)。
- virtualBucketTimes:虚拟节点倍数,默认为 160。
<function name="murmur-hash" class="io.mycat.route.function.PartitionByMurmurHash"> <property name="seed">0</property> <property name="count">3</property> <property name="virtualBucketTimes">160</property> </function>
-
自定义分片算法
- 原理:只需实现 io.mycat.route.function.PartitionAlgorithm 接口
- 适用场景:适用于复杂的分片需求。
public class CustomPartition implements PartitionAlgorithm { @Override public Integer calculate(String columnValue) { // 自定义分片逻辑 return Integer.parseInt(columnValue) % 3; } @Override public void init() { // 初始化逻辑 } @Override public String getPartition(int partitionNum) { return null; } }
七、使用 MyCAT 分库分表后查询操作
在使用 MyCAT 进行分库分表后,查询操作需要特别注意以下几点,以确保查询性能和数据一致性:
-
分片键的使用
- 明确分片键:查询条件中应尽量包含分片键(如 user_id),以便 MyCAT 能够直接定位到具体的分片,避免全表扫描。
-- 正确:包含分片键 SELECT * FROM user WHERE user_id = 1; -- 错误:不包含分片键,会导致全表扫描 SELECT * FROM user WHERE username = 'user1';
-
JOIN 操作
- 避免跨分片 JOIN:MyCAT 不支持跨分片的 JOIN 操作。如果需要进行 JOIN,应确保 JOIN 的表在同一个分片中,或者通过业务逻辑在应用层实现。
- 全局表:对于需要频繁 JOIN 的小表,可以将其配置为全局表(Global Table),MyCAT 会在每个分片中复制一份数据。
-- 错误:跨分片 JOIN SELECT * FROM user u JOIN order o ON u.user_id = o.user_id; -- 正确:确保 JOIN 的表在同一个分片 SELECT * FROM user u JOIN order o ON u.user_id = o.user_id WHERE u.user_id = 1;
-
聚合操作
- 分片内聚合:MyCAT 支持在单个分片内执行聚合操作(如 COUNT、SUM、AVG 等),但跨分片的聚合操作性能较差。
- 应用层聚合:如果需要进行跨分片的聚合操作,建议在应用层分别查询每个分片的数据,然后在应用层进行聚合。
-- 单分片聚合 SELECT COUNT(*) FROM user WHERE user_id = 1; -- 跨分片聚合(性能较差) SELECT COUNT(*) FROM user;
-
排序和分页
- 分片内排序:MyCAT 支持在单个分片内进行排序(ORDER BY),但跨分片的排序操作性能较差。
- 分页查询:跨分片的分页查询(LIMIT)性能较差,建议在查询条件中包含分片键,或者通过其他方式优化分页逻辑。
-- 单分片排序 SELECT * FROM user WHERE user_id = 1 ORDER BY create_time DESC; -- 跨分片排序(性能较差) SELECT * FROM user ORDER BY create_time DESC; -- 分页查询(性能较差) SELECT * FROM user LIMIT 10 OFFSET 20;
-
全局序列
- 唯一主键:在分库分表场景下,主键的唯一性需要通过全局序列(如 MyCAT 的全局序列功能)来保证。
- 避免冲突:确保每个分片生成的主键不会冲突。
-- 使用 MyCAT 全局序列生成主键 INSERT INTO user(user_id, username, email) VALUES(NEXT VALUE FOR MYCATSEQ_GLOBAL, 'user1', 'user1@example.com');
-
事务管理
- 分布式事务:MyCAT 支持单分片的事务,但不支持跨分片的分布式事务。如果需要跨分片的事务一致性,可以通过应用层的补偿机制(如 Saga 模式)来实现。
- 事务边界:尽量将事务控制在单个分片内,避免跨分片的事务操作。
// 单分片事务 @Transactional public void addUser(User user) { userMapper.insert(user); } // 跨分片事务(不支持) @Transactional public void addUserAndOrder(User user, Order order) { userMapper.insert(user); // 分片1 orderMapper.insert(order); // 分片2 }
-
数据一致性
- 数据同步:在分库分表场景下,数据的一致性需要通过应用层或工具(如 Canal、Debezium)来保证。
- 最终一致性:对于跨分片的数据操作,可以采用最终一致性方案,通过消息队列或定时任务来同步数据。
-
性能优化
- 索引优化:在每个分片的物理表上创建合适的索引,以提升查询性能。
- 查询优化:尽量避免复杂的查询操作,如子查询、嵌套查询等。
- 缓存:对于频繁查询的数据,可以使用缓存(如 Redis)来减少数据库的压力。