1. 为什么需要垂直分片:从单库压力说起
2019年我接手过一个电商后台系统,当时商品表已经增长到2000万条记录,而用户表只有300万条。每次大促期间,商品表的读写操作让整个数据库不堪重负,但用户表却相对空闲。这就是典型的"不同业务表负载不均"场景——也是垂直分片(Vertical Sharding)要解决的核心问题。
垂直分片与水平分片的本质区别在于:
- 水平分片:把同一张表的数据按行拆分到不同库(如订单按用户ID分片)
- 垂直分片:把不同表拆分到不同库(如用户表、商品表独立部署)
在ShardingSphere-JDBC中实现垂直分片后,那个电商系统的变化非常明显:
- 商品查询平均响应时间从1200ms降到280ms
- 用户服务与商品服务的资源隔离,故障影响范围缩小
- 针对商品表的单独优化(如增加缓存层)变得更容易实施
关键认知:垂直分片不是为解决单表数据量过大,而是为了应对不同业务表的资源竞争问题。当你的系统中存在明显"热点表"时,就该考虑垂直拆分了。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. ShardingSphere-JDBC垂直分片实现原理
2.1 核心架构设计
ShardingSphere-JDBC通过逻辑数据库(Logic Schema)的概念实现垂直分片。我们来看一个典型配置:
yaml复制# 定义逻辑数据库名称(应用看到的统一入口)
schemaName: ecommerce_db
# 配置实际数据源
dataSources:
user_ds:
url: jdbc:mysql://user-db:3306/user_db
product_ds:
url: jdbc:mysql://product-db:3306/product_db
# 配置表与数据源的映射关系
rules:
- !SHARDING
tables:
t_user:
actualDataNodes: user_ds.t_user
t_product:
actualDataNodes: product_ds.t_product
这个配置实现了:
- 应用层看到的仍是
ecommerce_db这个统一数据库 - 实际查询
t_user时路由到user_ds - 查询
t_product时路由到product_ds
2.2 SQL路由的关键过程
当执行SELECT * FROM t_product WHERE id = 123时:
- SQL解析器识别出表
t_product - 根据配置找到绑定关系
product_ds.t_product - 改写SQL为
SELECT * FROM product_db.t_product WHERE id = 123 - 通过
product_ds连接池执行改写后的SQL
特别注意:跨分片的JOIN查询需要特殊处理。比如
SELECT u.name, p.title FROM t_user u JOIN t_product p ON u.id = p.user_id会导致全表扫描,应该避免。
3. 生产环境配置实战
3.1 多数据源连接池配置
垂直分片对连接池管理有更高要求。建议采用以下配置策略:
yaml复制dataSources:
user_ds:
url: jdbc:mysql://user-db:3306/user_db
connectionTimeoutMilliseconds: 30000
idleTimeoutMilliseconds: 600000
maxLifetimeMilliseconds: 1800000
maxPoolSize: 50
minPoolSize: 10
product_ds:
url: jdbc:mysql://product-db:3306/product_db
maxPoolSize: 100 # 商品库通常需要更多连接
minPoolSize: 20
不同业务库的连接池需要差异化配置:
- 高频访问的库(如商品)配置更大连接池
- 低频但重要的库(如支付)配置更长的超时时间
- 监控每个数据源的连接使用率(建议配置预警阈值)
3.2 分布式事务处理
垂直分片后的事务处理是个难点。ShardingSphere支持多种方案:
| 方案类型 | 适用场景 | 性能影响 | 一致性保障 |
|---|---|---|---|
| LOCAL | 单分片操作 | 无 | 无 |
| XA | 强一致性要求 | 高 | 强 |
| BASE(Seata) | 最终一致性 | 中 | 弱 |
实际项目中,我们通常采用"尽量单分片事务+补偿机制"的策略。例如下单流程:
java复制// 伪代码示例
@Transactional(rollbackFor = Exception.class)
public void createOrder(Order order) {
// 1. 操作订单库(分片1)
orderMapper.insert(order);
// 2. 操作库存库(分片2)
inventoryService.reduceStock(order.getProductId(), order.getCount());
// 如果库存操作失败,会触发订单库回滚
}
4. 性能优化与踩坑记录
4.1 必须监控的关键指标
在垂直分片环境中,这些指标需要特别关注:
-
分片倾斜度:计算各分片负载差异率
code复制倾斜度 = (最大QPS - 最小QPS) / 平均QPS * 100%超过30%就需要考虑重新分片
-
跨分片查询比例:通过ShardingSphere的
sql_show配置记录SQL日志yaml复制props: sql.show: true -
连接池等待时间:特别是高峰期的连接获取延迟
4.2 真实案例:错误的索引设计
我们曾遇到一个性能问题:商品分片的查询突然变慢。最终发现是因为在商品表上同时存在:
idx_category(category_id)idx_seller(seller_id)idx_category_seller(category_id, seller_id)
这三个索引导致写入性能下降50%。解决方案是:
- 删除单独的
idx_category和idx_seller - 将
idx_category_seller改为idx_seller_category(因为按卖家查询更多) - 添加
idx_status用于高频的状态筛选
优化后,查询性能提升40%,写入速度恢复。
5. 进阶:与水平分片结合使用
当单个业务表数据量也很大时,需要垂直分片+水平分片组合使用。例如用户表按ID水平分片:
yaml复制rules:
- !SHARDING
tables:
t_user:
actualDataNodes: user_ds_${0..1}.t_user_${0..1}
databaseStrategy:
standard:
shardingColumn: user_id
preciseAlgorithmClassName: com.example.HashMod2DatabaseShardingAlgorithm
tableStrategy:
inline:
shardingColumn: user_id
algorithmExpression: t_user_${user_id % 2}
这种组合方案需要注意:
- 先垂直分片(按业务拆分),再水平分片(按数据量拆分)
- 避免跨多个维度的分片键(如不要同时按user_id和product_id分片)
- 分布式ID生成器变得尤为重要(推荐使用Snowflake)
我在实际项目中验证过,这种组合架构可以支撑亿级用户+千万级商品的生产环境。关键是要做好分片键的选择——应该选择最常用的查询条件作为分片键。
