一 什么是 ShardingSphere?
1.1 背景:为什么需要分库分表?
随着系统数据量和用户量不断增长,传统单体数据库容易遇到瓶颈:
写入/查询压力大:单表千万级数据,索引效率下降。
存储超限:单机磁盘空间和 IOPS 不堪重负。
水平扩展困难:业务难以横向拓展。
为了解决这些问题,开发者往往采用 分库分表 技术,将一张大表分成多个小表,分布在不同数据库中,实现水平扩展和负载均衡。
1.2 什么是 Apache ShardingSphere?
Apache ShardingSphere 是一款开源的 分布式数据库中间件框架,可以帮助开发者轻松实现:
分库分表
读写分离
分布式事务
数据脱敏
可观测性 & 扩展治理能力
ShardingSphere 核心组件包括:
组件 | 说明 |
---|---|
ShardingSphere-JDBC | JDBC 封装层,嵌入应用中,适用于微服务架构 |
ShardingSphere-Proxy | 独立部署的数据库代理,支持多语言访问 |
ShardingSphere-Sidecar(规划中) | 面向云原生场景,Service Mesh 结构 |
1.3 ShardingSphere-JDBC 的核心功能
功能 | 简要说明 |
---|---|
分库分表 | 按字段配置路由策略,自动将 SQL 分发到对应的数据库/表 |
读写分离 | 自动区分读写请求,路由到主/从数据库 |
灵活分片策略 | 支持取模、范围、Hint、自定义算法等多种策略 |
事务支持 | 支持本地事务和分布式事务(Seata/XA) |
透明化使用 | 对开发者来说使用方式与普通 JDBC 几乎一致,只需配置逻辑表名 |
二、实践部分:Java + H2 实现分库分表
示例:使用 Java 原生程序 + ShardingSphere-JDBC + H2 内存数据库,模拟一个电商订单系统按
user_id
分库,order_id
分表。
2.1 项目结构
使用 H2 数据库模拟两个库
ds0
,ds1
每个库中创建两个分表
t_order_0
,t_order_1
逻辑表名:
t_order
2.2 Maven 依赖配置
<dependencies>
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>shardingsphere-jdbc-core</artifactId>
<version>5.4.1</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>2.2.220</version>
</dependency>
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>5.0.1</version>
</dependency>
</dependencies>
2.3 核心代码结构
1. 创建真实表(物理分表)
public static void createTables() throws SQLException {
for (int i = 0; i < 2; i++) {
String db = "ds" + i;
try (Connection conn = DriverManager.getConnection("jdbc:h2:mem:" + db + ";DB_CLOSE_DELAY=-1;MODE=MySQL", "sa", "")) {
Statement stmt = conn.createStatement();
for (int j = 0; j < 2; j++) {
stmt.execute(String.format("""
CREATE TABLE IF NOT EXISTS t_order_%d (
order_id BIGINT PRIMARY KEY,
user_id INT,
status VARCHAR(50)
)
""", j));
}
}
}
}
2. 配置分库分表数据源
public static DataSource createDataSource() throws SQLException {
Map<String, DataSource> dataSourceMap = new HashMap<>();
for (int i = 0; i < 2; i++) {
HikariDataSource ds = new HikariDataSource();
ds.setJdbcUrl("jdbc:h2:mem:ds" + i + ";DB_CLOSE_DELAY=-1;MODE=MySQL");
ds.setUsername("sa");
ds.setPassword("");
dataSourceMap.put("ds" + i, ds);
}
ShardingTableRuleConfiguration orderTableRule = new ShardingTableRuleConfiguration(
"t_order", "ds${0..1}.t_order_${0..1}");
orderTableRule.setDatabaseShardingStrategy(
new StandardShardingStrategyConfiguration("user_id", "dbSharding"));
orderTableRule.setTableShardingStrategy(
new StandardShardingStrategyConfiguration("order_id", "tableSharding"));
ShardingRuleConfiguration config = new ShardingRuleConfiguration();
config.getTables().add(orderTableRule);
config.getShardingAlgorithms().put("dbSharding",
new AlgorithmConfiguration("INLINE", props("algorithm-expression", "ds${user_id % 2}")));
config.getShardingAlgorithms().put("tableSharding",
new AlgorithmConfiguration("INLINE", props("algorithm-expression", "t_order_${order_id % 2}")));
return ShardingSphereDataSourceFactory.createDataSource(dataSourceMap, List.of(config), new Properties());
}
3. 插入与查询数据
public static void insertOrder(DataSource ds, long orderId, int userId, String status) throws SQLException {
try (Connection conn = ds.getConnection()) {
PreparedStatement ps = conn.prepareStatement("INSERT INTO t_order (order_id, user_id, status) VALUES (?, ?, ?)");
ps.setLong(1, orderId);
ps.setInt(2, userId);
ps.setString(3, status);
ps.executeUpdate();
}
}
public static void queryOrders(DataSource ds) throws SQLException {
try (Connection conn = ds.getConnection()) {
ResultSet rs = conn.createStatement().executeQuery("SELECT * FROM t_order");
while (rs.next()) {
System.out.printf("order_id=%d, user_id=%d, status=%s%n",
rs.getLong("order_id"),
rs.getInt("user_id"),
rs.getString("status"));
}
}
}
2.4 程序入口
public static void main(String[] args) throws Exception {
createTables(); // 创建实际分表
DataSource shardingDataSource = createDataSource();
insertOrder(shardingDataSource, 1001, 1, "INIT");
insertOrder(shardingDataSource, 1002, 2, "PAID");
insertOrder(shardingDataSource, 1003, 3, "SHIPPED");
queryOrders(shardingDataSource);
}
输出示例
order_id=1001, user_id=1, status=INIT
order_id=1002, user_id=2, status=PAID
order_id=1003, user_id=3, status=SHIPPED
ShardingSphere 已自动根据你配置的策略将数据路由到对应的库和表!
总结
Apache ShardingSphere 提供了强大、灵活的分库分表能力,通过合理配置可以极大提升系统的性能与扩展性:
分片策略灵活,支持多种规则或自定义算法
配置简单,无需改动业务 SQL
兼容性强,支持 JDBC、Spring Boot、YAML 等多种使用方式
无论你是中小项目的快速原型,还是大规模高并发系统,ShardingSphere 都是一个值得一试的解决方案。
完整代码
package org.example;
import com.zaxxer.hikari.HikariDataSource;
import org.apache.shardingsphere.driver.api.ShardingSphereDataSourceFactory;
import org.apache.shardingsphere.infra.config.algorithm.AlgorithmConfiguration;
import org.apache.shardingsphere.sharding.api.config.ShardingRuleConfiguration;
import org.apache.shardingsphere.sharding.api.config.rule.ShardingTableRuleConfiguration;
import org.apache.shardingsphere.sharding.api.config.strategy.sharding.StandardShardingStrategyConfiguration;
import javax.sql.DataSource;
import java.sql.*;
import java.util.*;
public class ShardingH2Example {
public static void main(String[] args) throws Exception {
createTables(); // 注意:用 DriverManager 直连底层 db 建表
DataSource shardingDataSource = createDataSource();
// 插入测试数据
insertOrder(shardingDataSource, 1001, 1, "INIT");
insertOrder(shardingDataSource, 1002, 2, "PAID");
insertOrder(shardingDataSource, 1003, 3, "SHIPPED");
// 查询测试数据
queryOrders(shardingDataSource);
}
public static DataSource createDataSource() throws Exception {
Map<String, DataSource> dataSourceMap = new HashMap<>();
// 模拟两个库:ds0 和 ds1(分别用两个内存 H2 实例模拟)
for (int i = 0; i < 2; i++) {
HikariDataSource ds = new HikariDataSource();
ds.setJdbcUrl("jdbc:h2:mem:ds" + i + ";DB_CLOSE_DELAY=-1;MODE=MySQL");
ds.setUsername("sa");
ds.setPassword("");
dataSourceMap.put("ds" + i, ds);
}
// 分表规则配置
ShardingTableRuleConfiguration orderTableRule = new ShardingTableRuleConfiguration(
"t_order", "ds${0..1}.t_order_${0..1}");
// 分库策略(user_id)
orderTableRule.setDatabaseShardingStrategy(new StandardShardingStrategyConfiguration(
"user_id", "dbSharding"));
// 分表策略(order_id)
orderTableRule.setTableShardingStrategy(new StandardShardingStrategyConfiguration(
"order_id", "tableSharding"));
ShardingRuleConfiguration shardingRuleConfig = new ShardingRuleConfiguration();
shardingRuleConfig.getTables().add(orderTableRule);
Properties dbProps = new Properties();
dbProps.setProperty("algorithm-expression", "ds${user_id % 2}");
shardingRuleConfig.getShardingAlgorithms().put("dbSharding",
new AlgorithmConfiguration("INLINE", dbProps));
Properties tableProps = new Properties();
tableProps.setProperty("algorithm-expression", "t_order_${order_id % 2}");
shardingRuleConfig.getShardingAlgorithms().put("tableSharding",
new AlgorithmConfiguration("INLINE", tableProps));
return ShardingSphereDataSourceFactory.createDataSource(dataSourceMap, List.of(shardingRuleConfig), new Properties());
}
public static void createTables() throws SQLException {
for (int i = 0; i < 2; i++) {
String dbName = "ds" + i;
try (Connection conn = DriverManager.getConnection("jdbc:h2:mem:" + dbName + ";DB_CLOSE_DELAY=-1;MODE=MySQL", "sa", "")) {
Statement stmt = conn.createStatement();
for (int j = 0; j < 2; j++) {
String sql = String.format("""
CREATE TABLE IF NOT EXISTS t_order_%d (
order_id BIGINT PRIMARY KEY,
user_id INT,
status VARCHAR(50)
)
""", j);
stmt.execute(sql);
}
}
}
}
// 辅助方法用于手动连接底层 H2 数据源
private static HikariDataSource getH2DataSource(String name) {
HikariDataSource ds = new HikariDataSource();
ds.setJdbcUrl("jdbc:h2:mem:" + name + ";DB_CLOSE_DELAY=-1;MODE=MySQL");
ds.setUsername("sa");
ds.setPassword("");
return ds;
}
public static void insertOrder(DataSource ds, long orderId, int userId, String status) throws SQLException {
try (Connection conn = ds.getConnection()) {
PreparedStatement ps = conn.prepareStatement("INSERT INTO t_order (order_id, user_id, status) VALUES (?, ?, ?)");
ps.setLong(1, orderId);
ps.setInt(2, userId);
ps.setString(3, status);
ps.executeUpdate();
}
}
public static void queryOrders(DataSource ds) throws SQLException {
try (Connection conn = ds.getConnection()) {
ResultSet rs = conn.createStatement().executeQuery("SELECT * FROM t_order");
System.out.println("Query Results:");
while (rs.next()) {
System.out.printf("order_id: %d, user_id: %d, status: %s%n",
rs.getLong("order_id"),
rs.getInt("user_id"),
rs.getString("status"));
}
}
}
}