ShardingSphere 分库分表
什么是 ShardingSphere
"ShardingSphere 主要由三个核心组件构成:ShardingSphere-JDBC(嵌入式JDBC驱动)、ShardingSphere-JDBC(独立数据库代理)和ShardingSphere-UI(管理界面)。其中JDBC模式适合Java应用,Proxy模式适合多语言应用,UI提供可视化管理,三者共同构成完整的分布式数据库解决方案。"
什么是分库分表
分库分表 是两个方面,即分库 和分表:
- 分库:将一个数据库分成多个数据库,并部署到不同机器中
- 分表:将一个数据库表分成多个表
分库分表是为了解决单库单表数据量过大、并发压力高、存储瓶颈等问题,本质是"空间换时间",通过增加存储架构的复杂度,换取性能和扩展性的提升。
什么是垂直拆分
垂直拆分是按"列"或"业务模块"进行拆分,分为垂直分库 和垂直分表。
1. 垂直分库
-
定义 :把单一数据库按照
业务进行划分,专库专表 -
应用场景:功能模块分离、性能优化、安全性
-
举例:电商系统将用户信息存储在user_db,订单信息存储在order_db
sql-- 原始综合数据库 CREATE DATABASE ecommerce; -- 垂直分库后 CREATE DATABASE user_db; CREATE DATABASE order_db; -- user_db中 USE user_db; CREATE TABLE users (...); -- order_db中 USE order_db; CREATE TABLE orders (...);
2. 垂直分表
-
定义:操作数据库中某张表,把这张表中一部分字段数据存到一张新表里面,再把这张表另一 部分字段数据存到另外一张表里面
-
应用场景:减少表宽度、提高查询性能、提高索引效率
-
举例:用户表拆分为常用字段表和不常用字段表
sql-- 原始用户表 CREATE TABLE users ( user_id INT PRIMARY KEY, username VARCHAR(50), password VARCHAR(255), email VARCHAR(100), phone VARCHAR(20), address TEXT, -- 不常用字段 profile_picture BLOB -- 大字段 ); -- 垂直分表后 -- 热点表(常用字段) CREATE TABLE users_core ( user_id INT PRIMARY KEY, username VARCHAR(50), password VARCHAR(255), email VARCHAR(100), phone VARCHAR(20) ); -- 冷数据表(不常用字段) CREATE TABLE users_profile ( user_id INT PRIMARY KEY, address TEXT, profile_picture BLOB );
什么是水平拆分
水平拆分是按"行"进行拆分,分为水平分库 和水平分表。
水平拆分的常见规则
范围分片:按时间范围(如订单表按"下单时间"拆分)
哈希分片:对分片字段(如用户ID)进行哈希计算,均匀分布数据
sqlshard_id = hash(user_id) % shard_count地理位置分片:按地理位置(如"华北地区用户"、"华东地区用户")
业务标识分片:按业务标识(如按"商家ID"拆分订单表)
1. 水平分库
-
定义:将表的数据行分配到不同的数据库实例中
-
应用场景:数据量大、高并发
-
举例:订单表按用户ID哈希分片
sql-- 按用户ID哈希分片到不同数据库 -- order_db_1:存储用户ID哈希值为0-9999的订单 -- order_db_2:存储用户ID哈希值为10000-19999的订单
2. 水平分表
-
定义:将表的数据行分成多个表,每个表只包含部分行
-
应用场景:数据量大、时间序列数据
-
举例:订单表按时间范围分表
sql-- 按年份分表 CREATE TABLE orders_2023 (...); -- 2023年订单 CREATE TABLE orders_2024 (...); -- 2024年订单
ShardingSphere-JDBC
简介
"ShardingSphere-JDBC 作为嵌入式 JDBC 驱动,主要作用是解决数据库的水平扩展问题:通过数据分片分散单表压力,通过读写分离提升读性能,通过分布式事务保证跨库一致性,同时对应用层完全透明,只需修改数据源配置,无需修改业务代码。"
"ShardingSphere-JDBC 与 ShardingSphere-Proxy 的区别在于:JDBC 是嵌入式驱动,适合 Java 应用;Proxy 是独立代理,适合多语言应用。两者共同构成 ShardingSphere 的完整解决方案,但 JDBC 通常性能更高、延迟更低。"
水平分表
具体 Maven 依赖:
xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.1.RELEASE</version>
<relativePath/>
</parent>
<groupId>com.lxx</groupId>
<artifactId>sharding-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>sharding-demo</name>
<description>sharding-demo</description>
<properties>
<!-- 明确指定Java 8 -->
<java.version>8</java.version>
<!-- 确保Maven编译器使用Java 8 -->
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.20</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.0.5</version>
</dependency>
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-spring-boot-starter</artifactId>
<version>4.0.0-RC1</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<!-- 明确指定Java 8 -->
<source>1.8</source>
<target>1.8</target>
<!-- 确保没有release配置,因为Java 8不支持 -->
<!-- <release>8</release> -->
</configuration>
</plugin>
</plugins>
</build>
</project>
需求说明:
- 创建数据库 course_db
- 创建表 course_1 、 course_2
- 约定规则:如果添加的课程 id 为偶数添加到 course_1 中,奇数添加到 course_2 中。
sql
create database course_db;
use course_db;
create table course_1 (
cid bigint(20) primary key ,
cname varchar(50) not null,
user_id bigint(20) not null ,
status varchar(10) not null
) engine = InnoDB;
create table course_2 (
cid bigint(20) primary key ,
cname varchar(50) not null,
user_id bigint(20) not null ,
status varchar(10) not null
) engine = InnoDB;
配置对应实体类以及 Mapper
java
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Course {
private Long cid;
private String cname;
private Long userId;
private String status;
}
java
@Repository
public interface CourseMapper extends BaseMapper<Course> {
}
启动类配置
java
@MapperScan("com.lxx.shardingdemo.mapper")
@SpringBootApplication
public class ShardingDemoApplication {
public static void main(String[] args) {
SpringApplication.run(ShardingDemoApplication.class, args);
}
}
配置 Sharding-JDBC 分片策略
application.yml内容:
yml
################### 水平分表 #########################
spring:
main:
# 允许覆盖同名Bean定义(解决ShardingSphere与Druid的dataSource冲突)
allow-bean-definition-overriding: true
shardingsphere:
# 数据源配置(使用Druid连接池)
datasource:
names: m1
m1:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/course_db?characterEncoding=UTF8&autoReconnect=true&serverTimezone=Asia/Shanghai
username: root
password: 123456
# 系统属性配置
props:
sql:
# 开启SQL日志输出(用于调试分片策略)
show: true
# 分片规则配置
sharding:
tables:
course:
# 数据源:固定为 m1(只有一个数据源)
# 表:course_$->{1..2} 表示在 m1 数据源中存在两个表:course_1 和 course_2
# 实际数据节点:2个: m1.course_1、m1.course_2
# 含义:只进行表分片(分表),不分库。所有数据都存储在 m1 这一个数据库中,但按规则分布在两个表中。
actual-data-nodes: m1.course_$->{1..2}
# 主键生成策略(使用Snowflake算法)
key-generator:
column: cid
type: SNOWFLAKE
# 分表策略(根据cid值进行分片)
table-strategy:
inline:
sharding-column: cid
# 分片算法表达式:cid为偶数→course_1,奇数→course_2
algorithm-expression: course_$->{cid % 2 + 1}
测试
java
@Test
public void addCourse() {
for (int i = 0; i < 20; i++) {
Course course = new Course();
//cid由我们设置的策略,雪花算法进行生成
course.setCname("Java");
course.setUserId(100L);
course.setStatus("Normal");
courseMapper.insert(course);
}
}
运行结果:
cid:1220064809920233472 为偶数插入course_1,
cid:1220064809689546753 为奇数插入course_2
Actual SQL: m1 ::: INSERT INTO course_1 (cname, user_id, status, cid) VALUES (?, ?, ?, ?) ::: [Java, 100, Normal, 1220064809920233472]
Actual SQL: m1 ::: INSERT INTO course_2 (cname, user_id, status, cid) VALUES (?, ?, ?, ?) ::: [Java, 100, Normal, 1220064809689546753]
测试
java
@Test
public void findCourse() {
QueryWrapper<Course> wrapper = new QueryWrapper<>();
wrapper.eq("cid", 1220064809689546753L);
Course course = courseMapper.selectOne(wrapper);
System.out.println(course);
}
运行结果:1220064809689546753L 为奇数查询course_2
Actual SQL: m1 ::: SELECT cid,cname,user_id,status FROM course_2
水平分库
需求:
- 创建两个数据库,edu_db_1、edu_db_2。
- 每个库中包含:course_1、course_2。
- 数据库规则:userid 为偶数添加到 edu_db_1 库,奇数添加到 edu_db_2。
- 表规则:如果添加的 cid 为偶数添加到 course_1 中,奇数添加到 course_2 中。
sql
create database edu_db_1;
create database edu_db_2;
use edu_db_1;
create table course_1 (
`cid` bigint(20) primary key,
`cname` varchar(50) not null,
`user_id` bigint(20) not null,
`status` varchar(10) not null
);
create table course_2 (
`cid` bigint(20) primary key,
`cname` varchar(50) not null,
`user_id` bigint(20) not null,
`status` varchar(10) not null
);
use edu_db_2;
create table course_1 (
`cid` bigint(20) primary key,
`cname` varchar(50) not null,
`user_id` bigint(20) not null,
`status` varchar(10) not null
);
create table course_2 (
`cid` bigint(20) primary key,
`cname` varchar(50) not null,
`user_id` bigint(20) not null,
`status` varchar(10) not null
);
配置 Sharding-JDBC 分片策略
application.yml内容:
yml
################### 水平分库 #########################
spring:
main:
# 允许覆盖同名Bean定义(解决ShardingSphere与Druid的dataSource冲突)
allow-bean-definition-overriding: true
shardingsphere:
# 数据源配置(使用Druid连接池)
datasource:
# 分布式数据源别名列表
names: m1,m2
m1:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/edu_db_1?characterEncoding=UTF8&autoReconnect=true&serverTimezone=Asia/Shanghai
username: root
password: 123456
m2:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/edu_db_2?characterEncoding=UTF8&autoReconnect=true&serverTimezone=Asia/Shanghai
username: root
password: 123456
# 系统属性配置
props:
sql:
# 开启SQL日志输出(用于调试分片策略)
show: true
# 分片规则配置
sharding:
tables:
course:
# 数据源:m$->{1..2} 表示数据源有 m1 和 m2 两个
# 表:course_$->{1..2} 表示表有 course_1 和 course_2 两个
# 实际数据节点:4个:m1.course_1、m1.course_2、m2.course_1、m2.course_2
# 含义:进行双库双表的分片策略,既分库又分表。
actual-data-nodes: m$->{1..2}.course_$->{1..2}
# 主键生成策略(使用Snowflake算法)
key-generator:
column: cid
type: SNOWFLAKE
# 分库策略(根据user_id值进行分片)
database-strategy:
inline:
sharding-column: user_id
algorithm-expression: m$->{user_id % 2 + 1}
# 分表策略(根据cid值进行分片)
table-strategy:
inline:
sharding-column: cid
algorithm-expression: course_$->{cid % 2 + 1}
测试
java
@Test
public void addCourse() {
Course course1 = new Course();
course1.setCname("python");
course1.setUserId(100L);
course1.setStatus("Normal");
courseMapper.insert(course1);
Course course2 = new Course();
course2.setCname("python");
course2.setUserId(111L);
course2.setStatus("Normal");
courseMapper.insert(course2);
}
运行结果:
user_id:100 为偶 数插入m1数据库 , cid:1220068117682585601 为奇 数插入course_2表
user_id:111 为奇 数插入m2数据库 , cid:1220068118945071104 为偶 数插入course_1表
Actual SQL: m1 ::: INSERT INTO course_2 (cname, user_id, status, cid) VALUES (?, ?, ?, ?) ::: [python, 100, Normal, 1220068117682585601]
Actual SQL: m2 ::: INSERT INTO course_1 (cname, user_id, status, cid) VALUES (?, ?, ?, ?) ::: [python, 111, Normal, 1220068118945071104]
测试
java
@Test
public void findCourse() {
QueryWrapper<Course> wrapper = new QueryWrapper<>();
wrapper.eq("cid", 1220068118945071104L);
wrapper.eq("user_id",111);
Course course = courseMapper.selectOne(wrapper);
System.out.println(course);
}
运行结果:user_id:111 为奇 数查询m2数据库 , cid:1220068118945071104 为偶 数查询course_1表
Actual SQL: m2 ::: SELECT cid,cname,user_id,status FROM course_1
垂直分库
需求:
我们再额外创建一个 user_db 数据库。当我们查询用户信息就去 user_db,课程信息就去 edu_db_1、edu_db_2。
sql
create database user_db;
use user_db;
create table t_user(
`user_id` bigint(20) primary key,
`username` varchar(100) not null,
`status` varchar(50) not null
);
配置对应实体类以及 Mapper
java
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("t_user")
public class User {
private Long userId;
private String username;
private String status;
}
java
@Repository
public interface UserMapper extends BaseMapper<User> {
}
配置 Sharding-JDBC 分片策略
application.yml内容:
yml
##################### 垂直分库 #########################
spring:
main:
# 允许覆盖同名Bean定义(解决ShardingSphere与Druid的dataSource冲突)
allow-bean-definition-overriding: true
shardingsphere:
# 数据源配置(m0/m1/m2为分库别名,m0专用于用户表,m1/m2用于课程表)
datasource:
names: m0,m1,m2 # 分布式数据源别名列表
# 专库专表:用户表t_user独立使用m0数据源
m0:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/user_db?characterEncoding=UTF8&autoReconnect=true&serverTimezone=Asia/Shanghai
username: root
password: 123456
# 课程表分库1:edu_db_1
m1:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/edu_db_1?characterEncoding=UTF8&autoReconnect=true&serverTimezone=Asia/Shanghai
username: root
password: 123456
# 课程表分库2:edu_db_2
m2:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/edu_db_2?characterEncoding=UTF8&autoReconnect=true&serverTimezone=Asia/Shanghai
username: root
password: 123456
# 系统属性配置(调试关键参数)
props:
sql:
# 开启SQL日志输出(仅用于开发环境调试分片策略)
show: true
# 分片规则配置(双库双表分片策略)
sharding:
# 专库专表配置:t_user表独立使用m0数据源
tables:
t_user: # 用户表(专库专表,不参与分库分表)
# 实际数据节点:m0数据库中的t_user表
actual-data-nodes: m0.t_user
# 主键生成策略
key-generator:
column: user_id
type: SNOWFLAKE
course: # 课程表(双库双表分片)
# 实际数据节点:m1/m2分库 + course_1/course_2分表
actual-data-nodes: m$->{1..2}.course_$->{1..2}
# 主键生成策略
key-generator:
column: cid
type: SNOWFLAKE
# 分库策略(根据user_id分片,确保相同user_id在同库)
database-strategy:
inline:
sharding-column: user_id # 分片键(决定路由到哪个分库)
algorithm-expression: m$->{user_id % 2 + 1} # 分库算法:user_id % 2 → m1/m2
# 说明:user_id为偶数→m1,奇数→m2(如0→m1, 1→m2, 2→m1)
# 分表策略(根据cid分片,确保相同cid在同表)
table-strategy:
inline:
sharding-column: cid # 分片键(决定路由到哪个分表)
algorithm-expression: course_$->{cid % 2 + 1} # 分表算法:cid % 2 → course_1/course_2
# 说明:cid为偶数→course_1,奇数→course_2(如0→course_1, 1→course_2)
测试
java
@Test
public void addUserAndCourse() {
User user1 = new User();
user1.setUsername("Jack");
user1.setStatus("Normal");
userMapper.insert(user1);
User use2r = new User();
user2.setUsername("Max");
user2.setStatus("Normal");
userMapper.insert(user2);
Course course1 = new Course();
course1.setCname("python");
course1.setUserId(100L);
course1.setStatus("Normal");
courseMapper.insert(course1);
Course course2 = new Course();
course2.setCname("python");
course2.setUserId(111L);
course2.setStatus("Normal");
courseMapper.insert(course2);
}
运行结果:
插入t_user表都使用数据源m0
user_id:100 为偶 数插入m1数据库 , cid:1220075920027549697 为奇 数插入course_2表
user_id:111 为奇 数插入m2数据库 , cid:1220075920530866176 为偶 数插入course_1表
Actual SQL: m0 ::: INSERT INTO t_user (username, status, user_id) VALUES (?, ?, ?) ::: [Jack, Normal, 1220075919247409153]
Actual SQL: m0 ::: INSERT INTO t_user (username, status, user_id) VALUES (?, ?, ?) ::: [Max, Normal, 1220075919805251584]
Actual SQL: m1 ::: INSERT INTO course_2 (cname, user_id, status, cid) VALUES (?, ?, ?, ?) ::: [python, 100, Normal, 1220075920027549697]
Actual SQL: m2 ::: INSERT INTO course_1 (cname, user_id, status, cid) VALUES (?, ?, ?, ?) ::: [python, 111, Normal, 1220075920530866176]
公共表
概念
- 存储固定数据的表,表数据很少发生变化,查询时经常要进行关联。
- 在每个数据库中都创建出相同结构公共表。
- 操作公共表时,同时操作添加了公共表的数据库中的公共表,添加记录时,同时添加,删除时,同时删除。
在多个数据库中创建公共表
sql
# use user_db;
# use edu_db_1;
use edu_db_2;
create table t_dict(
`dict_id` bigint(20) primary key,
`status` varchar(100) not null,
`value` varchar(100) not null
);
配置公共表的实体类和 mapper
java
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("t_dict")
public class Dict {
private Long dictId;
private String status;
private String value;
}
java
@Repository
public interface DictMapper extends BaseMapper<Dict> {
}
配置 Sharding-JDBC 分片策略
application.yml内容:
yml
##################### 垂直分库 #########################
spring:
main:
# 允许覆盖同名Bean定义(解决ShardingSphere与Druid的dataSource冲突)
allow-bean-definition-overriding: true
shardingsphere:
# 数据源配置(m0/m1/m2为分库别名,m0专用于用户表,m1/m2用于课程表)
datasource:
names: m0,m1,m2 # 分布式数据源别名列表
# 专库专表:用户表t_user独立使用m0数据源
m0:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/user_db?characterEncoding=UTF8&autoReconnect=true&serverTimezone=Asia/Shanghai
username: root
password: 123456
# 课程表分库1:edu_db_1
m1:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/edu_db_1?characterEncoding=UTF8&autoReconnect=true&serverTimezone=Asia/Shanghai
username: root
password: 123456
# 课程表分库2:edu_db_2
m2:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/edu_db_2?characterEncoding=UTF8&autoReconnect=true&serverTimezone=Asia/Shanghai
username: root
password: 123456
# 系统属性配置(调试关键参数)
props:
sql:
# 开启SQL日志输出(仅用于开发环境调试分片策略)
show: true
# 分片规则配置(双库双表分片策略)
sharding:
# 专库专表配置:t_user表独立使用m0数据源
tables:
t_user: # 用户表(专库专表,不参与分库分表)
# 实际数据节点:m0数据库中的t_user表
actual-data-nodes: m0.t_user
# 主键生成策略
key-generator:
column: user_id
type: SNOWFLAKE
course: # 课程表(双库双表分片)
# 实际数据节点:m1/m2分库 + course_1/course_2分表
actual-data-nodes: m$->{1..2}.course_$->{1..2}
# 主键生成策略
key-generator:
column: cid
type: SNOWFLAKE
# 分库策略(根据user_id分片,确保相同user_id在同库)
database-strategy:
inline:
sharding-column: user_id # 分片键(决定路由到哪个分库)
algorithm-expression: m$->{user_id % 2 + 1} # 分库算法:user_id % 2 → m1/m2
# 说明:user_id为偶数→m1,奇数→m2(如0→m1, 1→m2, 2→m1)
# 分表策略(根据cid分片,确保相同cid在同表)
table-strategy:
inline:
sharding-column: cid # 分片键(决定路由到哪个分表)
algorithm-expression: course_$->{cid % 2 + 1} # 分表算法:cid % 2 → course_1/course_2
# 说明:cid为偶数→course_1,奇数→course_2(如0→course_1, 1→course_2)
t_dict:
key-generator:
column: dict_id # 主键列名
type: SNOWFLAKE
# 公共表配置
broadcast-tables: t_dict
测试
java
@Test
public void addDict() {
Dict dict = new Dict();
dict.setStatus("Normal");
dict.setValue("启用");
dictMapper.insert(dict);
}
运行结果:m0、m1、m2 同时插入
Actual SQL: m0 ::: INSERT INTO t_dict (status, value, dict_id) VALUES (?, ?, ?) ::: [Normal, 启用, 1220090484970487809]
Actual SQL: m1 ::: INSERT INTO t_dict (status, value, dict_id) VALUES (?, ?, ?) ::: [Normal, 启用, 1220090484970487809]
Actual SQL: m2 ::: INSERT INTO t_dict (status, value, dict_id) VALUES (?, ?, ?) ::: [Normal, 启用, 1220090484970487809]
测试
java
@Test
public void deleteDict() {
QueryWrapper<Dict> wrapper = new QueryWrapper<>();
wrapper.eq("dict_id", 1220090484970487809L);
dictMapper.delete(wrapper);
}
运行结果:m0、m1、m2 同时删除
Actual SQL: m0 ::: DELETE FROM t_dict WHERE dict_id = ? ::: [1220090484970487809]
Actual SQL: m1 ::: DELETE FROM t_dict WHERE dict_id = ? ::: [1220090484970487809]
Actual SQL: m2 ::: DELETE FROM t_dict WHERE dict_id = ? ::: [1220090484970487809]
读写分离
什么是读写分离
读写分离 是一种数据库架构设计模式,将数据库的 写操作(INSERT/UPDATE/DELETE)和读操作(SELECT) 分别路由到不同的数据库实例上:
- ✅ 写操作 :发送到主库(Master)
- ✅ 读操作 :发送到从库(Slave)
与主从复制的关系
- 主从复制是技术基础:主库将变更同步到从库
- 读写分离是应用层逻辑:决定读操作应路由到哪个库
主从复制原理:
基于二进制日志(binlog)的异步复制机制 ,通过I/O线程 和SQL线程协作,将主库(Master)的数据变更同步到从库(Slave)。
工作流程(4步)
- 主库记录变更:执行写操作 → 记录到binlog
- 主库发送binlog:binlog dump线程发送binlog给从库I/O线程
- 从库存储binlog:I/O线程将binlog写入relay log
- 从库执行变更:SQL线程解析relay log,执行SQL更新数据
搭建MySQL主从复制
开启bin-log日志
修改主服务器Master
sh
修改配置文件:vim /etc/my.cnf
#主服务器唯一ID
server-id=1
#启用二进制日志
log-bin=mysql-bin
注意 :一定要区分[mysqld]和[mysql],以上配置放在
[mysqld]
修改从服务器slave
sh
修改配置文件:vim /etc/my.cnf
#从服务器唯一ID
server-id=2
#启用中继日志,从主服务器上同步日志文件记录到本地
relay-log=mysql-relay
重启两台服务器的mysql
sh
service mysql restart
以上完毕之后我们登录主服务器的 MySQL
sh
mysql -u root -p
在主服务器上建立帐户并授权slave
sql
GRANT REPLICATION SLAVE ON *.* to 'slave'@'%' identified by '123456';
GRANT REPLICATION SLAVE:授予 复制从库权限(REPLICATION SLAVE)
ON *.*:第一个*:所有数据库 ,第二个*:所有表 。该用户可以对主库上所有数据库和表进行复制操作
TO 'slave'@'%':'slave':用户名 (创建的数据库用户);'%':主机名通配符 (表示允许从任何IP地址连接)
IDENTIFIED BY '123456':设置用户密码
查询Master的状态
sql
show master status;
+------------------+----------+--------------+------------------+-------------------+
| File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
+------------------+----------+--------------+------------------+-------------------+
| mysql-bin.000001 | 430 | | | |
+------------------+----------+--------------+------------------+-------------------+
注意:
执行完此步骤后不要再操作主服务器MYSQL,防止主服务器状态值变化。
配置从服务器Slave
sql
change master to master_host='192.168.1.101',master_user='slave',master_password='123456',master_log_file='mysql-bin.000001',master_log_pos=430;
注意: 注意不要断开,430数字前后无单引号。
启动从服务器复制功能
sql
start slave;
检查从服务器复制功能状态
sql
show slave status \G;
注意:
Slave_IO及Slave_SQL进程必须正常运行,即YES状态,否则都是错误的状态(如:其中一个NO均属错误)。
主从服务器测试
主服务器Mysql,建立数据库,并在这个库中建表插入一条数据
sql
mysql> create database test;
Query OK, 1 row affected (0.00 sec)
mysql> use test;
Database changed
mysql> create table dog(id int(3),name varchar(255));
Query OK, 0 rows affected (0.00 sec)
mysql> insert into dog values(001,'zhuti');
Query OK, 1 row affected (0.00 sec)
从服务器Mysql查询
sql
mysql>show databases;
mysql>use test;
mysql>select * from dog;
Sharding-JDBC 实现读写分离
Sharding-JDBC 实现读写分离则是根据sql 语句语义分析 ,当 sql 语句有 insert、update、delete 时,Sharding-JDBC 就把这次操作在主数据库上执行;当 sql 语句有 select 时,就会把这次操作在从数据库上执行,从而实现读写分离过程。
但 Sharding-JDBC 并不会做数据同步,数据同步是配置 MySQL 后由 MySQL 自己完成的。
搭建环境成功后我们在主库和从库上都建库建表:
sql
create database user_db;
use user_db;
create table t_user(
`user_id` bigint(20) primary key,
`username` varchar(100) not null,
`status` varchar(50) not null
);
配置 Sharding-JDBC 分片策略
application.yml内容:
yml
################### 读写分离 #########################
spring:
main:
# 允许覆盖同名Bean定义(解决ShardingSphere与Druid的dataSource冲突)
allow-bean-definition-overriding: true
shardingsphere:
# 数据源配置(m0为主库,s0为从库,用于用户表读写分离)
datasource:
names: m0,s0 # 分布式数据源别名列表(m0=主库, s0=从库)
# 主库配置(user_db数据库,用于写操作)
m0:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://192.168.1.101:3306/user_db?characterEncoding=UTF8&autoReconnect=true&serverTimezone=Asia/Shanghai
username: root
password: 123456
# 从库配置(user_db数据库,用于读操作)
s0:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://192.168.1.102:3306/user_db?characterEncoding=UTF8&autoReconnect=true&serverTimezone=Asia/Shanghai
username: root
password: 123456
# 系统属性配置(调试关键参数)
props:
sql:
# 开启SQL日志输出(仅用于开发环境调试,生产环境建议关闭)
show: true
# 读写分离规则配置(主从复制)
sharding:
master-slave-rules:
ds0: # 读写分离数据源别名(逻辑数据源名)
master-data-source-name: m0 # 主库数据源别名
slave-data-source-names: s0 # 从库数据源别名(可配置多个,用逗号分隔)
# 说明:写操作路由到m0,读操作路由到s0(默认读从库)
tables:
t_user:
# 实际数据节点:ds0数据库中的t_user
actual-data-nodes: ds0.t_user
# 主键生成策略(使用雪花算法生成分布式ID)
key-generator:
column: user_id
type: SNOWFLAKE
测试
java
@Test
public void addUser() {
User user = new User();
user.setUsername("Jack");
user.setStatus("Normal");
userMapper.insert(user);
}
运行结果:在主库m0插入
Actual SQL: m0 ::: INSERT INTO t_user (username, status, user_id) VALUES (?, ?, ?) ::: [Jack, Normal, 1220097776008822785]
测试
java
@Test
public void findUser() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("user_id", 1220097776008822785L);
userMapper.selectOne(wrapper);
}
运行结果:在从库s0查询
Actual SQL: s0 ::: SELECT user_id,username,status FROM t_user WHERE user_id = ? ::: [1220097776008822785]
ShardingSphere-JDBC
待续......