在MySQL中,分库分表是一种常用的数据库优化策略,特别是在数据量巨大时,可以有效提高查询性能和系统的可扩展性。以下简单介绍下分库分表的概念:
分库分表的策略主要有以下两种:
垂直分库分表:
优点:
缺点:
水平分库分表:
优点:
缺点:
虽然分库分表的策略已经很明确,但是要如何才能在开发中实现分库分表的效果呢?目前已经有很多成熟的中间件,只需要根据业务需求选择合适的中间件引入到项目中,就可以轻松的实现分库分表的目的。以下是现在比较常用的中间件:
在Spring Boot项目中整合MyCAT实现分库分表是一个复杂的过程,涉及到数据库配置、MyCAT配置以及Spring Boot的相关设置。
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>
添加依赖
在 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();
}
}
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)
<!-- 通过 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)
<function name="hash-int" class="io.mycat.route.function.PartitionByHashMod">
<property name="count">3</property>
</function>
注意:
1.确保count的值与实际数据库中分片的数量一致。
2.user_id列应该是一个适合哈希运算的字段,通常是主键或唯一标识符。
3.根据实际需求,可能需要调整分片算法和分片数量。
枚举分片(PartitionByFileMap)
<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):
<function name="mod-long" class="io.mycat.route.function.PartitionByMod">
<property name="count">2</property>
</function>
日期分片(PartitionByDate)
<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)
<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>
自定义分片算法
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 进行分库分表后,查询操作需要特别注意以下几点,以确保查询性能和数据一致性:
分片键的使用
-- 正确:包含分片键
SELECT * FROM user WHERE user_id = 1;
-- 错误:不包含分片键,会导致全表扫描
SELECT * FROM user WHERE username = 'user1';
JOIN 操作
-- 错误:跨分片 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;
聚合操作
-- 单分片聚合
SELECT COUNT(*) FROM user WHERE user_id = 1;
-- 跨分片聚合(性能较差)
SELECT COUNT(*) FROM user;
排序和分页
-- 单分片排序
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 全局序列生成主键
INSERT INTO user(user_id, username, email) VALUES( 1909569696596889600, 'user1', '[email protected]');
事务管理
// 单分片事务
@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
}
数据一致性
性能优化