目录
一、前言
随着业务数据量的增长,单库单表逐渐无法满足高并发、大数据量的需求。分库分表成为解决这一问题的有效方案。
ShardingSphere是Apache旗下的开源分布式数据库中间件,本文档基于实际项目,讲解ShardingSphere-JDBC的配置和使用方法。
二、什么是ShardingSphere-JDBC
ShardingSphere-JDBC是Apache ShardingSphere的核心产品,定位为轻量级Java框架,在JDBC层提供分库分表服务。
核心特点:
- 透明化 - 对业务代码零侵入,就像使用普通JDBC一样
- 轻量级 - 无需额外部署,仅作为JDBC驱动存在
- 配置简单 - 通过YAML配置即可实现分库分表
适用场景:
- 需要分库分表的应用
- 基于MyBatis、JPA等ORM框架的应用
三、为什么需要分库分表
3.1 单库单表的瓶颈
当数据量达到千万级时,会遇到以下问题:
- 性能瓶颈 - 单表数据量大,查询变慢
- 存储瓶颈 - 磁盘IO成为瓶颈
- 连接限制 - 数据库连接数不足
3.2 分库分表的优势
- 提升性能 - 单表数据量减少,查询速度提升
- 提高并发 - 多库分担读写压力
- 易于扩展 - 可以动态增加数据源
四、核心概念
4.1 逻辑表与物理表
逻辑表 - 应用层看到的表,比如t_user
物理表 - 实际存储在数据库中的表,比如t_user_0、t_user_1
yaml
t_user: # 逻辑表
actual-data-nodes: ds$->{0..1}.t_user_$->{0..1} # 物理表
4.2 分片键
决定数据路由到哪个分片的字段,必须包含在SQL中才能正确路由。
java
// 正确:包含分片键,精确路由
SELECT * FROM t_user WHERE user_id = 100;
// 错误:不包含分片键,全路由(查询所有分片)
SELECT * FROM t_user WHERE username = 'zhangsan';
4.3 分片算法
ShardingSphere通过分片算法决定数据路由到哪个分片。
| 算法类型 | 说明 | 适用场景 |
|---|---|---|
| INLINE | 基于Groovy表达式的简单算法 | 简单的取模分片 |
| STANDARD | 标准分片算法 | 需要精确和范围查询 |
本项目使用INLINE算法,配置简单,性能优秀。
4.4 主键生成器
分布式环境下,需要全局唯一的主键生成策略。
| 生成器类型 | 说明 | 特点 |
|---|---|---|
| SNOWFLAKE | 雪花算法 | 分布式、高性能、趋势递增 |
4.5 广播表
在所有数据源中都存在的表,数据完全一致。
特点:
- 插入、更新、删除操作会在所有数据源执行
- 查询操作只在一个数据源执行
- 适用于字典表、配置表等小表
配置示例:
yaml
broadcast-tables:
- t_dict
4.6 绑定表
多个表使用相同的分片键和分片算法,确保关联数据在同一分片。
配置示例:
yaml
binding-tables:
- t_order,t_order_item
优势:
- 避免跨库JOIN
- 提高查询性能
- 简化事务处理
五、快速开始
5.1 添加依赖
在pom.xml中添加ShardingSphere-JDBC依赖:
xml
<dependencies>
<!-- ShardingSphere-JDBC核心依赖 -->
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId>
<version>5.2.1</version>
</dependency>
<!-- MySQL驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- MyBatis -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.3.1</version>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.38</version>
<scope>provided</scope>
</dependency>
</dependencies>
5.2 配置数据源
在application.yml中配置4个数据源:
yaml
spring:
shardingsphere:
datasource:
names: ds0,ds1,ds2,ds3
ds0:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://127.0.0.1:3306/test_db1?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8
username: root
password: 123456
hikari:
minimum-idle: 10
maximum-pool-size: 50
connection-timeout: 30000
idle-timeout: 600000
max-lifetime: 1800000
connection-test-query: SELECT 1
ds1:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://127.0.0.1:3306/test_db2?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8
username: root
password: 123456
hikari:
minimum-idle: 10
maximum-pool-size: 50
connection-timeout: 30000
idle-timeout: 600000
max-lifetime: 1800000
connection-test-query: SELECT 1
ds2:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://127.0.0.1:3306/test_db3?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8
username: root
password: 123456
hikari:
minimum-idle: 10
maximum-pool-size: 50
connection-timeout: 30000
idle-timeout: 600000
max-lifetime: 1800000
connection-test-query: SELECT 1
ds3:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://127.0.0.1:3306/test_db4?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8
username: root
password: 123456
hikari:
minimum-idle: 10
maximum-pool-size: 50
connection-timeout: 30000
idle-timeout: 600000
max-lifetime: 1800000
connection-test-query: SELECT 1
配置说明:
names- 定义所有数据源的名称hikari- HikariCP连接池配置minimum-idle- 最小空闲连接数maximum-pool-size- 最大连接池大小connection-timeout- 连接超时时间(毫秒)
5.3 配置分片规则
yaml
spring:
shardingsphere:
rules:
sharding:
tables:
# 用户表配置
t_user:
# 实际数据节点: ds0.t_user_0, ds0.t_user_1, ds1.t_user_0, ds1.t_user_1
actual-data-nodes: ds$->{0..1}.t_user_$->{0..1}
# 分库策略
database-strategy:
standard:
sharding-column: user_id
sharding-algorithm-name: user-db-inline
# 分表策略
table-strategy:
standard:
sharding-column: user_id
sharding-algorithm-name: user-table-inline
# 主键生成策略
key-generate-strategy:
column: user_id
key-generator-name: snowflake
配置说明:
actual-data-nodes- 定义物理表的分布ds$->{0..1}- 表示ds0和ds1两个数据源t_user_$->{0..1}- 表示t_user_0和t_user_1两个表- 组合后: ds0.t_user_0, ds0.t_user_1, ds1.t_user_0, ds1.t_user_1
5.4 配置分片算法
yaml
spring:
shardingsphere:
rules:
sharding:
sharding-algorithms:
# 用户表分库算法
user-db-inline:
type: INLINE
props:
algorithm-expression: 'ds$->{ (Math.abs(user_id.hashCode()) % 4).intdiv(2) }'
# 用户表分表算法
user-table-inline:
type: INLINE
props:
algorithm-expression: 't_user_$->{ Math.abs(user_id.hashCode()) % 2 }'
算法说明:
-
分库算法 :
ds$->{ (Math.abs(user_id.hashCode()) % 4).intdiv(2) }- 计算user_id的hashCode取绝对值
- 对4取模,得到0、1、2、3
- 整除2,得到0、1
- 最终路由到ds0或ds1
-
分表算法 :
t_user_$->{ Math.abs(user_id.hashCode()) % 2 }- 计算user_id的hashCode取绝对值
- 对2取模,得到0、1
- 最终路由到t_user_0或t_user_1
数据分布示例:
ini
user_id = 100
- 分库: (100 % 4).intdiv(2) = 0 → ds0
- 分表: 100 % 2 = 0 → t_user_0
- 最终路由: ds0.t_user_0
5.5 配置主键生成器
yaml
spring:
shardingsphere:
rules:
sharding:
key-generators:
snowflake:
type: SNOWFLAKE
雪花算法原理:
- 1位符号位
- 41位时间戳(毫秒级)
- 10位机器ID
- 12位序列号
5.6 配置广播表和绑定表
yaml
spring:
shardingsphere:
rules:
sharding:
# 绑定表配置
binding-tables:
- t_order,t_order_item
# 广播表配置
broadcast-tables:
- t_dict
5.7 配置属性
yaml
spring:
shardingsphere:
props:
# 显示SQL(生产环境建议关闭)
sql-show: true
# 允许没有分片列的插入通过主键生成器
check-table-metadata-enabled: false
5.8 创建实体类
java
package com.example.demo.model;
import lombok.Data;
import java.time.LocalDateTime;
@Data
public class User {
/**
* 用户ID(主键,由ShardingSphere自动生成)
*/
private Long userId;
/**
* 用户名
*/
private String username;
/**
* 邮箱
*/
private String email;
/**
* 年龄
*/
private Integer age;
/**
* 创建时间
*/
private LocalDateTime createdTime;
/**
* 更新时间
*/
private LocalDateTime updatedTime;
}
5.9 创建Mapper接口
java
package com.example.demo.mapper;
import com.example.demo.model.User;
import org.apache.ibatis.annotations.*;
import java.util.List;
@Mapper
public interface UserMapper {
/**
* 插入用户
* 注意:不包含user_id字段,由ShardingSphere自动生成
*/
@Insert("INSERT INTO t_user (username, email, age) VALUES (#{username}, #{email}, #{age})")
int insert(User user);
/**
* 根据ID更新用户
*/
@Update("UPDATE t_user SET username = #{username}, email = #{email}, age = #{age} WHERE user_id = #{userId}")
int updateById(User user);
/**
* 根据ID查询用户
*/
@Select("SELECT * FROM t_user WHERE user_id = #{userId}")
User selectById(Long userId);
/**
* 根据用户名查询用户
* 注意:此查询不包含分片键,会导致全路由
*/
@Select("SELECT * FROM t_user WHERE username = #{username}")
User selectByUsername(String username);
/**
* 查询所有用户
* 注意:此查询不包含分片键,会导致全路由
*/
@Select("SELECT * FROM t_user")
List<User> selectAll();
/**
* 根据ID删除用户
*/
@Delete("DELETE FROM t_user WHERE user_id = #{userId}")
int deleteById(Long userId);
}
重要说明:
- INSERT语句中不包含分片键字段(user_id),由ShardingSphere自动生成
- 查询语句最好包含分片键,否则会导致全路由,性能较差
5.10 创建Controller层
java
package com.example.demo.controller;
import com.example.demo.mapper.UserMapper;
import com.example.demo.model.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/users")
public class UserController {
@Autowired
private UserMapper userMapper;
@PostMapping
public String insert(@RequestBody User user) {
int result = userMapper.insert(user);
return result > 0 ? "User inserted successfully" : "Failed to insert user";
}
@PutMapping
public String update(@RequestBody User user) {
int result = userMapper.updateById(user);
return result > 0 ? "User updated successfully" : "Failed to update user";
}
@GetMapping("/{userId}")
public User selectById(@PathVariable Long userId) {
return userMapper.selectById(userId);
}
@GetMapping("/username/{username}")
public User selectByUsername(@PathVariable String username) {
return userMapper.selectByUsername(username);
}
@GetMapping
public List<User> selectAll() {
return userMapper.selectAll();
}
@DeleteMapping("/{userId}")
public String deleteById(@PathVariable Long userId) {
int result = userMapper.deleteById(userId);
return result > 0 ? "User deleted successfully" : "Failed to delete user";
}
}
5.11 初始化数据库
执行SQL脚本创建数据库和表:
sql
-- 创建数据库
CREATE DATABASE IF NOT EXISTS test_db1 CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE DATABASE IF NOT EXISTS test_db2 CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- 创建用户表
CREATE TABLE IF NOT EXISTS t_user_0
(
user_id BIGINT PRIMARY KEY,
username VARCHAR(50) NOT NULL,
email VARCHAR(100),
age INT,
created_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4
COLLATE = utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS t_user_1
(
user_id BIGINT PRIMARY KEY,
username VARCHAR(50) NOT NULL,
email VARCHAR(100),
age INT,
created_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4
COLLATE = utf8mb4_unicode_ci;
5.12 启动项目
bash
mvn spring-boot:run
5.13 测试API
bash
# 插入用户
curl -X POST http://localhost:8080/users \
-H "Content-Type: application/json" \
-d '{"username":"zhangsan","email":"zhangsan@example.com","age":25}'
# 根据ID查询用户
curl http://localhost:8080/users/100
# 根据用户名查询用户
curl http://localhost:8080/users/username/zhangsan
# 查询所有用户
curl http://localhost:8080/users
# 更新用户
curl -X PUT http://localhost:8080/users \
-H "Content-Type: application/json" \
-d '{"userId":100,"username":"lisi","email":"lisi@example.com","age":26}'
# 删除用户
curl -X DELETE http://localhost:8080/users/100
六、分片策略详解
6.1 标准分片策略
标准分片策略是最常用的分片策略,支持精确分片和范围查询。
配置示例:
yaml
t_user:
actual-data-nodes: ds$->{0..1}.t_user_$->{0..1}
database-strategy:
standard:
sharding-column: user_id
sharding-algorithm-name: user-db-inline
table-strategy:
standard:
sharding-column: user_id
sharding-algorithm-name: user-table-inline
6.2 行表达式分片算法
行表达式分片算法是最简单的分片算法,使用Groovy表达式定义分片规则。
配置示例:
yaml
spring:
shardingsphere:
rules:
sharding:
sharding-algorithms:
user-db-inline:
type: INLINE
props:
algorithm-expression: 'ds$->{ (Math.abs(user_id.hashCode()) % 4).intdiv(2) }'
user-table-inline:
type: INLINE
props:
algorithm-expression: 't_user_$->{ Math.abs(user_id.hashCode()) % 2 }'
常用表达式:
| 表达式 | 说明 | 示例 |
|---|---|---|
$->{column} |
获取分片列的值 | user_id |
$->{column % n} |
取模运算 | user_id % 2 |
$->{Math.abs(column.hashCode())} |
取绝对值 | Math.abs(user_id.hashCode()) |
$->{column.intdiv(n)} |
整除运算 | (user_id % 4).intdiv(2) |
示例:
- 简单取模
yaml
algorithm-expression: 't_user_$->{user_id % 2}'
- Hash取模
yaml
algorithm-expression: 't_user_$->{Math.abs(user_id.hashCode()) % 2}'
注意事项:
- 表达式必须返回字符串
- 表达式中的变量名必须与分片列名一致
- 复杂表达式建议使用自定义算法
6.3 本项目分片策略总结
用户表 (t_user)
- 数据源: ds0, ds1 (对应数据库 test_db1, test_db2)
- 分片键: user_id
- 分库策略: 按 user_id % 4 后整除2 (偶数分到ds0, 奇数分到ds1)
- 分表策略: 按 user_id % 2 分表
- 表结构: 两个数据源共包含 t_user_0 和 t_user_1 四个分表
订单表 (t_order)
- 数据源: ds2, ds3 (对应数据库 test_db3, test_db4)
- 分片键: order_id
- 分库算法: 按 order_id % 2 映射到 ds2, ds3
- 分表算法: 按 order_id % 4 分片
- 表结构: 每个数据源包含 t_order_0 到 t_order_3 四个分表
订单项表 (t_order_item)
- 数据源: ds2, ds3 (对应数据库 test_db3, test_db4)
- 分片键: order_id
- 与订单表为绑定表关系
广播表 (t_dict)
- 在所有数据源中都存在
- 数据完全一致
七、高级特性
7.1 Hint分片策略
Hint分片策略通过Hint指定分片,适用于需要强制路由的场景。
使用场景:
- 批量操作时指定分片提高性能
- 需要强制路由到特定分片的场景
- 数据迁移场景
配置示例:
yaml
spring:
shardingsphere:
rules:
sharding:
tables:
t_order:
actual-data-nodes: ds$->{2..3}.t_order_$->{0..3}
table-strategy:
hint:
sharding-algorithm-name: order-hint
sharding-algorithms:
order-hint:
type: INLINE
props:
algorithm-expression: 't_order_$->{value}'
使用Hint路由:
java
package com.example.demo.controller;
import com.example.demo.mapper.OrderMapper;
import com.example.demo.model.Order;
import org.apache.shardingsphere.infra.hint.HintManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/orders")
public class OrderController {
@Autowired
private OrderMapper orderMapper;
/**
* 使用Hint强制路由到指定分片查询
*
* @param orderId 订单ID
* @param tableIndex 表索引(0-3)
* @return 订单对象
*/
@GetMapping("/hint/{orderId}/{tableIndex}")
public Order selectByHint(@PathVariable Long orderId, @PathVariable int tableIndex) {
try (HintManager hintManager = HintManager.getInstance()) {
// 设置Hint值,指定路由到哪个分片
hintManager.addTableShardingValue("t_order", tableIndex);
return orderMapper.selectById(orderId);
}
}
/**
* 批量查询,指定多个分片
*
* @param tableIndices 表索引列表(0-3)
* @return 订单列表
*/
@GetMapping("/hint/batch/{tableIndices}")
public List<Order> batchSelectByHint(@PathVariable List<Integer> tableIndices) {
try (HintManager hintManager = HintManager.getInstance()) {
// 设置多个Hint值
for (int tableIndex : tableIndices) {
hintManager.addTableShardingValue("t_order", tableIndex);
}
return orderMapper.selectAll();
}
}
}
注意事项:
- HintManager使用后需要关闭,可以使用try-with-resources
- Hint只在当前线程有效
- Hint优先级高于其他分片策略
7.2 读写分离
读写分离是提升系统性能的重要手段,将读操作分发到从库,写操作在主库执行。
配置示例:
yaml
spring:
shardingsphere:
datasource:
names: master,slave0,slave1
# 主库配置
master:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://127.0.0.1:3306/test_db?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8
username: root
password: 123456
hikari:
minimum-idle: 10
maximum-pool-size: 50
connection-timeout: 30000
# 从库0配置
slave0:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://127.0.0.1:3307/test_db?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8
username: root
password: 123456
hikari:
minimum-idle: 10
maximum-pool-size: 50
connection-timeout: 30000
# 从库1配置
slave1:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://127.0.0.1:3308/test_db?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8
username: root
password: 123456
hikari:
minimum-idle: 10
maximum-pool-size: 50
connection-timeout: 30000
rules:
# 读写分离规则
readwrite-splitting:
data-sources:
# 读写分离数据源配置
readwrite_ds:
type: Static
props:
# 写数据源
write-data-source-name: master
# 读数据源列表
read-data-source-names: slave0,slave1
# 负载均衡算法
load-balancer-name: round_robin
# 负载均衡算法
load-balancers:
round_robin:
type: ROUND_ROBIN
random:
type: RANDOM
负载均衡算法:
| 算法类型 | 说明 | 适用场景 |
|---|---|---|
| ROUND_ROBIN | 轮询 | 从库性能相近 |
| RANDOM | 随机 | 从库性能相近 |
强制读主库:
java
package com.example.demo.controller;
import com.example.demo.mapper.UserMapper;
import com.example.demo.model.User;
import org.apache.shardingsphere.infra.hint.HintManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/users")
public class UserController {
@Autowired
private UserMapper userMapper;
/**
* 强制从主库读取数据
* 用于需要读取最新数据的场景
*
* @param userId 用户ID
* @return 用户对象
*/
@GetMapping("/master/{userId}")
public User selectFromMaster(@PathVariable Long userId) {
try (HintManager hintManager = HintManager.getInstance()) {
// 设置从主库读取
hintManager.setWriteRouteOnly();
return userMapper.selectById(userId);
}
}
}
注意事项:
- 读写分离依赖MySQL主从复制
- 主从复制有延迟,可能读到旧数据
- 需要读取最新数据时,使用Hint强制读主库
- 确保从库数据与主库一致
7.3 数据加密
ShardingSphere支持数据加密,保护敏感数据。
配置示例:
yaml
spring:
shardingsphere:
rules:
# 加密规则
encrypt:
tables:
t_user:
columns:
# 邮箱加密配置
email:
cipher-column: email_cipher # 加密列
assisted-query-column: email_assisted # 辅助查询列
encryptor-name: aes_encryptor # 加密器名称
# 手机号加密配置
phone:
cipher-column: phone_cipher
encryptor-name: aes_encryptor
# 加密器配置
encryptors:
aes_encryptor:
type: AES
props:
aes-key-value: 1234567890123456 # AES密钥,16位
实体类调整:
java
package com.example.demo.model;
import lombok.Data;
import java.time.LocalDateTime;
@Data
public class User {
private Long userId;
private String username;
/**
* 邮箱(逻辑列,自动加密)
*/
private String email;
/**
* 手机号(逻辑列,自动加密)
*/
private String phone;
private Integer age;
private LocalDateTime createdTime;
private LocalDateTime updatedTime;
}
数据库表结构:
sql
CREATE TABLE IF NOT EXISTS t_user_0
(
user_id BIGINT PRIMARY KEY,
username VARCHAR(50) NOT NULL,
email_cipher VARCHAR(255), -- 加密后的邮箱
email_assisted VARCHAR(255), -- 辅助查询列
phone_cipher VARCHAR(255), -- 加密后的手机号
age INT,
created_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4
COLLATE = utf8mb4_unicode_ci;
加密算法类型:
| 算法类型 | 说明 | 适用场景 |
|---|---|---|
| AES | AES加密 | 通用加密 |
| MD5 | MD5加密 | 不可逆加密 |
注意事项:
- 加密后无法直接查询,需要使用辅助查询列
- 加密会影响性能,谨慎使用
- 密钥需要安全存储,建议使用密钥管理服务
7.4 分布式主键生成策略
除了Snowflake算法,ShardingSphere还支持其他主键生成策略。
配置示例:
yaml
spring:
shardingsphere:
rules:
sharding:
key-generators:
# 雪花算法
snowflake:
type: SNOWFLAKE
props:
max-vibration-offset: 1 # 最大抖动偏移量
max-tolerate-time-difference-milliseconds: 10 # 最大容忍时间差(毫秒)
主键生成器类型:
| 生成器类型 | 说明 | 特点 |
|---|---|---|
| SNOWFLAKE | 雪花算法 | 分布式、高性能、趋势递增 |
| UUID | UUID | 无序、长度长 |
| NIL | 不生成主键 | 需要手动指定 |
使用建议:
- 优先使用Snowflake算法
- 性能敏感场景考虑调整max-tolerate-time-difference-milliseconds
- 确保主键唯一性
7.5 SQL解析与改写
ShardingSphere支持SQL解析和改写,这是其核心能力之一。
功能说明:
- 自动路由 - 根据分片键自动路由到目标分片
- 结果归并 - 合并多个分片的结果
- SQL改写 - 修改SQL以适应分片场景
示例:
java
// 原始SQL
SELECT * FROM t_user WHERE user_id = 100
// 改写后的SQL(路由到ds0.t_user_0)
SELECT * FROM ds0.t_user_0 WHERE user_id = 100
使用建议:
- 理解SQL改写原理,优化SQL语句
- 避免全路由查询,性能较差
- 使用绑定表避免跨库JOIN
7.6 配置中心
ShardingSphere支持配置中心,实现动态配置管理。
支持的配置中心:
- Zookeeper
- Nacos
- Etcd
- Apollo
配置示例:
yaml
spring:
shardingsphere:
mode:
type: Cluster
repository:
type: Zookeeper
props:
namespace: sharding-sphere
server-lists: 127.0.0.1:2181
retry-interval-milliseconds: 500
max-retries: 3
time-to-live-milliseconds: 5000
优势:
- 配置动态更新,无需重启
- 配置集中管理
- 配置版本控制
使用建议:
- 生产环境使用配置中心
- 配置变更需要灰度发布
- 配置需要备份
八、生产环境最佳实践
7.1 配置优化
数据源配置:
yaml
spring:
shardingsphere:
datasource:
ds0:
hikari:
minimum-idle: 10
maximum-pool-size: 50
connection-timeout: 30000
idle-timeout: 600000
max-lifetime: 1800000
connection-test-query: SELECT 1
ShardingSphere配置:
yaml
spring:
shardingsphere:
props:
# 生产环境关闭SQL显示
sql-show: false
# 允许没有分片列的插入通过主键生成器
check-table-metadata-enabled: false
# 核心执行线程池大小
kernel-executor-size: 16
7.2 性能优化
1. 使用绑定表
yaml
spring:
shardingsphere:
rules:
sharding:
binding-tables:
- t_order,t_order_item
优势:
- 避免跨库JOIN
- 提高查询性能
- 简化事务处理
2. 避免全路由
java
// 不推荐:不包含分片键,全路由
List<User> users = userMapper.selectAll();
// 推荐:包含分片键,精确路由
User user = userMapper.selectById(100L);
3. 合理使用索引
sql
-- 在分片键上创建索引
CREATE INDEX idx_user_id ON t_user(user_id);
-- 在常用查询字段上创建索引
CREATE INDEX idx_username ON t_user(username);
7.3 监控告警
监控指标:
-
数据库指标
- 连接数使用率
- 慢查询数量
- 锁等待时间
-
应用指标
- SQL执行时间
- SQL错误率
- 分片路由时间
-
业务指标
- 请求响应时间
- 请求成功率
- 请求吞吐量
7.4 安全加固
1. SQL注入防护
java
// 不推荐:字符串拼接SQL
String sql = "SELECT * FROM t_user WHERE username = '" + username + "'";
// 推荐:使用参数化查询
@Select("SELECT * FROM t_user WHERE username = #{username}")
User selectByUsername(String username);
2. 权限控制
sql
-- 创建只读用户
CREATE USER 'readonly'@'%' IDENTIFIED BY 'password';
GRANT SELECT ON test_db.* TO 'readonly'@'%';
-- 创建读写用户
CREATE USER 'readwrite'@'%' IDENTIFIED BY 'password';
GRANT SELECT, INSERT, UPDATE, DELETE ON test_db.* TO 'readwrite'@'%';
7.5 运维建议
1. 定期备份
bash
# 备份数据库
mysqldump -h 127.0.0.1 -u root -p test_db1 > test_db1_backup.sql
# 恢复数据库
mysql -h 127.0.0.1 -u root -p test_db1 < test_db1_backup.sql
2. 定期清理
sql
-- 清理过期数据
DELETE FROM t_user WHERE created_time < DATE_SUB(NOW(), INTERVAL 1 YEAR);
-- 优化表
OPTIMIZE TABLE t_user_0;
OPTIMIZE TABLE t_user_1;
九、常见问题与解决方案
9.1 分片键自动生成问题
问题: INSERT语句不包含分片键,导致路由失败。
解决方案:
yaml
spring:
shardingsphere:
props:
check-table-metadata-enabled: false
java
@Insert("INSERT INTO t_user (username, email, age) VALUES (#{username}, #{email}, #{age})")
int insert(User user);
9.2 全路由问题
问题: 查询不包含分片键,导致全路由,性能较差。
解决方案:
java
// 不推荐:全路由
List<User> users = userMapper.selectAll();
// 推荐:精确路由
User user = userMapper.selectById(100L);
9.3 跨库JOIN问题
问题: 跨库JOIN性能较差或无法执行。
解决方案:
yaml
spring:
shardingsphere:
rules:
sharding:
binding-tables:
- t_order,t_order_item
9.4 主键冲突问题
问题: 分布式主键生成器配置错误,导致主键冲突。
解决方案:
yaml
spring:
shardingsphere:
rules:
sharding:
key-generators:
snowflake:
type: SNOWFLAKE
9.5 连接池耗尽问题
问题: 连接池配置不合理,导致连接池耗尽。
解决方案:
yaml
spring:
shardingsphere:
datasource:
ds0:
hikari:
minimum-idle: 10
maximum-pool-size: 50
connection-timeout: 30000
9.6 Hint分片不生效问题
问题: 使用HintManager指定分片后,查询仍然路由到所有分片。
解决方案:
java
// 确保使用try-with-resources或手动关闭HintManager
try (HintManager hintManager = HintManager.getInstance()) {
hintManager.addTableShardingValue("t_order", 0);
return orderMapper.selectById(orderId);
}
// 或者手动关闭
HintManager hintManager = HintManager.getInstance();
try {
hintManager.addTableShardingValue("t_order", 0);
return orderMapper.selectById(orderId);
} finally {
hintManager.close();
}
9.7 读写分离主从延迟问题
问题: 读写分离导致主从延迟,读取到旧数据。
解决方案:
java
// 强制读主库
try (HintManager hintManager = HintManager.getInstance()) {
hintManager.setWriteRouteOnly();
return userMapper.selectById(userId);
}
十、总结
ShardingSphere-JDBC是一个功能强大的分库分表中间件,本文档详细介绍了其配置和使用方法。
核心要点:
- 分片键选择 - 选择高频查询字段作为分片键
- 分片算法 - 使用INLINE算法,配置简单,性能优秀
- 绑定表 - 使用绑定表避免跨库JOIN
- 主键生成 - 使用Snowflake算法保证唯一性
- 避免全路由 - 查询语句最好包含分片键
最佳实践:
- 优先使用INLINE算法,性能好
- 避免全路由查询,性能差
- 使用绑定表,避免跨库操作
- 配置合理的连接池大小
- 生产环境关闭sql-show
- 定期备份,防止数据丢失
参考资料:
- ShardingSphere官方文档: shardingsphere.apache.org/
- ShardingSphere GitHub: github.com/apache/shar...