MySQL 分库分表与读写分离:ShardingSphere 实战指南
前言
当单表数据超过千万级,单机 MySQL 已无法满足业务需求。分库分表是解决数据量膨胀的标准方案,但同时也引入了分布式架构的复杂性。本文讲解分库分表的核心策略,以及如何使用 ShardingSphere 实现读写分离和水平分片。
什么时候需要分库分表
分库分表不是银弹,过早分片会增加系统复杂度。建议按以下顺序评估:
1. 单表行数超过 1000 万或单表数据文件超过 10GB
2. QPS 超过单机 MySQL 上限(InnoDB 大约 1-2 万 QPS)
3. 主从复制延迟持续居高不下,影响业务读一致性
4. 单机磁盘空间不足,无法再扩展
在分库分表之前,先尝试以下优化手段:读写分离、SQL 优化、索引优化、缓存层(Redis)。分库分表是最后的选择。
分库分表的核心策略
垂直拆分
垂直分库:按业务模块将表拆分到不同数据库。
垂直分表:将大表的字段按访问频率拆分。
-- 垂直分表示例:订单表拆分
-- orders_main 表(高频字段)
CREATE TABLE orders_main (
id BIGINT PRIMARY KEY,
user_id BIGINT NOT NULL,
total_amount DECIMAL(10,2) NOT NULL,
status VARCHAR(20) NOT NULL,
created_at DATETIME NOT NULL,
INDEX idx_user_id (user_id),
INDEX idx_created_at (created_at)
);
-- orders_detail 表(低频字段)
CREATE TABLE orders_detail (
id BIGINT PRIMARY KEY,
order_id BIGINT NOT NULL,
product_details JSON,
INDEX idx_order_id (order_id)
);
水平拆分
水平分库:将同一张表的行拆分到不同数据库。
水平分表:将同一张表的行拆分到同一数据库的不同表。
分片键(Sharding Key)选择原则:
- 选择查询条件中最频繁使用的列
- 选择数据分布均匀的列(避免数据倾斜)
- 选择不经常变更的列
分片算法
取余(Hash)分片:按分片键的 Hash 值取模。优点是数据分布均匀;缺点是扩容时需要迁移大量数据。
范围分片:按分片键的值范围划分。优点是扩容方便;缺点是容易产生热点。
一致性哈希:环形哈希空间,减少扩容时的数据迁移量。
ShardingSphere-JDBC 实战
ShardingSphere 是 Apache 顶级项目,提供数据分片、分布式事务、读写分离等能力。ShardingSphere-JDBC 是轻量级 Java 库,通过 JDBC 层面拦截 SQL 实现分片,对应用代码无侵入。
# application.yml 配置 ShardingSphere-JDBC
spring:
datasource:
driver-class-name: org.apache.shardingsphere.driver.core.ShardingSphereDriver
url: jdbc:shardingsphere:classpath:sharding.yaml
# sharding.yaml - 分片规则配置
schemaName: shaoda
dataSources:
ds_0:
dataSourceClassName: com.zaxxer.hikari.HikariDataSource
driverClassName: com.mysql.cj.jdbc.Driver
jdbcUrl: jdbc:mysql://192.168.1.10:3306/shaoda_tech?useSSL=false
username: root
password: Admin@2026!
ds_1:
dataSourceClassName: com.zaxxer.hikari.HikariDataSource
driverClassName: com.mysql.cj.jdbc.Driver
jdbcUrl: jdbc:mysql://192.168.1.11:3306/shaoda_tech?useSSL=false
username: root
password: Admin@2026!
rules:
- !SHARDING
tables:
orders:
actualDataNodes: ds_0.orders_0,ds_0.orders_1,ds_0.orders_2,ds_0.orders_3,ds_0.orders_4,ds_0.orders_5,ds_0.orders_6,ds_0.orders_7,ds_1.orders_8,ds_1.orders_9,ds_1.orders_10,ds_1.orders_11,ds_1.orders_12,ds_1.orders_13,ds_1.orders_14,ds_1.orders_15
databaseStrategy:
standard:
shardingColumn: user_id
shardingAlgorithmName: orders_database_algorithm
tableStrategy:
standard:
shardingColumn: user_id
shardingAlgorithmName: orders_table_algorithm
bindingTables:
- orders
shardingAlgorithms:
orders_database_algorithm:
type: INLINE
props:
algorithm-expression: ds_0
orders_table_algorithm:
type: INLINE
props:
algorithm-expression: orders_0
keyGenerators:
snowflake:
type: SNOWFLAKE
props:
worker-id: 1
// Java 应用层代码(无需修改,ShardingSphere 自动拦截)
@Service
public class OrderService {
@Autowired
private JdbcTemplate jdbcTemplate;
// 按 user_id 分片键查询,自动路由到正确的库和表
public List<Order> getUserOrders(Long userId, int limit) {
String sql = "SELECT * FROM orders WHERE user_id = ? ORDER BY created_at DESC LIMIT ?";
return jdbcTemplate.query(sql, new Object[]{userId, limit}, orderRowMapper);
}
// 插入订单,ShardingSphere 自动分配分片
public void createOrder(Order order) {
String sql = "INSERT INTO orders (user_id, total_amount, status, created_at) " +
"VALUES (?, ?, ?, NOW())";
jdbcTemplate.update(sql, order.getUserId(), order.getTotalAmount(), order.getStatus());
}
}
读写分离配置
ShardingSphere 支持配置主从复制架构下的读写分离,从库自动承担读请求,主库处理写请求。
# sharding.yaml - 读写分离配置
dataSources:
ds_master:
dataSourceClassName: com.zaxxer.hikari.HikariDataSource
driverClassName: com.mysql.cj.jdbc.Driver
jdbcUrl: jdbc:mysql://192.168.1.10:3306/shaoda_tech
username: root
password: Admin@2026!
ds_slave_0:
dataSourceClassName: com.zaxxer.hikari.HikariDataSource
driverClassName: com.mysql.cj.jdbc.Driver
jdbcUrl: jdbc:mysql://192.168.1.11:3306/shaoda_tech
username: root
password: Admin@2026!
ds_slave_1:
dataSourceClassName: com.zaxxer.hikari.HikariDataSource
driverClassName: com.mysql.cj.jdbc.Driver
jdbcUrl: jdbc:mysql://192.168.1.12:3306/shaoda_tech
username: root
password: Admin@2026!
rules:
- !READWRITE_SPLITTING
dataSources:
readwrite_ds:
writeDataSourceName: ds_master
readDataSourceNames:
- ds_slave_0
- ds_slave_1
loadBalancerName: round_robin
loadBalancers:
round_robin:
type: ROUND_ROBIN
常见问题
Q1:分片键选择不当会有什么后果?
最常见的问题是数据倾斜,导致某些分片数据量远超其他分片。另一个问题是跨分片查询频繁,导致应用层需要多次查询再聚合。分片键一旦确定很难更改,设计前要充分考虑业务查询模式。
Q2:扩容(Resharding)怎么处理?
这是分库分表最大的挑战之一。方案包括:提前规划足够的分片数(留足余量)、使用一致性哈希减少数据迁移量、或使用 ShardingSphere 的弹性扩容功能。
Q3:ShardingSphere 与 ShardingSphere-JDBC 和 ShardingSphere-Proxy 如何选型?
ShardingSphere-JDBC 适合 Java 技术栈,对应用透明,性能损耗小(~5%),但绑定应用进程。ShardingSphere-Proxy 是独立进程,支持任意语言客户端,但多一层网络转发。建议:Java 生态优先 JDBC,异构语言或多语言场景用 Proxy。
延伸阅读
- MySQL 主从复制配置
- MySQL 慢查询分析与优化
- ShardingSphere 官方文档:https://shardingsphere.apache.org/