ShardingSphere-JDBC 的分片策略(Sharding Strategy) 是分库分表功能的核心,它定义了数据如何根据分片键(Sharding Key)被路由到不同的数据库或数据表的规则。一个完整的分片策略由两部分组成:分片键(Sharding Key) 和分片算法(Sharding Algorithm)。
核心公式: 分片策略 = 分片键 + 分片算法
一、 分片键 (Sharding Key)
- 定义: 用于进行分片计算的数据库表字段。它是决定数据最终落到哪个物理库、哪个物理表的关键依据。
- 重要性:
- 选择恰当的分片键对性能、扩展性和查询效率至关重要。
- 应选择数据分布均匀、业务查询频繁使用(如 WHERE 条件、JOIN 条件)的字段。
- 常见分片键:
user_id
(用户ID)order_id
(订单ID)tenant_id
(租户ID)product_id
(商品ID)create_time
(创建时间 - 常用于按时间范围分片)
- 多分片键: ShardingSphere 支持使用多个字段作为复合分片键(如
user_id
+order_type
),需要配置ComplexShardingStrategy
并使用ComplexKeysShardingAlgorithm
。
二、 分片算法 (Sharding Algorithm)
分片算法是执行具体路由计算的逻辑。ShardingSphere 提供了多种内置算法,也支持完全自定义。主要分为两大类:
-
标准分片算法 (
StandardShardingAlgorithm
)- 作用: 主要处理精确分片场景,即 SQL 条件中包含
=
和IN
操作符的分片键查询。 - 核心接口:
doSharding(availableTargetNames, shardingValue)
:根据传入的分片值计算目标数据源或表。availableTargetNames
:所有可用的目标库名或表名集合(如["ds0", "ds1"]
或["t_order_0", "t_order_1"]
)。shardingValue
:包含分片键、逻辑表名和本次 SQL 中该分片键的实际值(或值列表)。
- 常用内置实现:
INLINE
: 基于 Groovy 表达式的轻量级算法。最常用,推荐优先考虑。- 配置示例 (
algorithm-expression
):- 库分片:
ds${user_id % 2}
(用户ID模2,分配到 ds0 或 ds1) - 表分片:
t_order_${order_id % 16}
(订单ID模16,分配到 t_order_0 到 t_order_15)
- 库分片:
- 配置示例 (
HASH_MOD
: 哈希取模算法(先对分片值做哈希,再取模)。MOD
: 简单取模算法(直接对分片值取模)。VOLUME_RANGE
: 基于分片值范围的分片算法(需要配置range-lower
和range-upper
定义每个分片承载的数据范围)。BOUNDARY_RANGE
: 基于分片值边界列表的分片算法(需配置sharding-ranges
指定精确边界)。CLASS_BASED
: 指向一个自定义实现了StandardShardingAlgorithm
接口的 Java 类。用于实现复杂或业务特定的分片逻辑。
- 作用: 主要处理精确分片场景,即 SQL 条件中包含
-
范围分片算法 (
RangeShardingAlgorithm
)- 作用: 处理范围分片场景,即 SQL 条件中包含
BETWEEN ... AND ...
、>
,<
,>=
,<=
操作符的分片键查询。通常与标准分片算法配合使用 (StandardShardingAlgorithm
+RangeShardingAlgorithm
=ClassBasedShardingAlgorithm
)。 - 核心接口:
doSharding(availableTargetNames, rangeShardingValue)
:根据传入的分片值范围计算目标数据源或表集合。rangeShardingValue
:包含分片键、逻辑表名和本次 SQL 中该分片键的值范围。
- 常用内置实现: 通常需要与
CLASS_BASED
结合,或者直接实现RangeShardingAlgorithm
接口。 - 示例场景: 按
create_time
按月分表 (t_order_202301
,t_order_202302
…)。查询WHERE create_time BETWEEN '2023-01-01' AND '2023-03-31'
时,范围算法需要计算出涉及t_order_202301
,t_order_202302
,t_order_202303
三个表。
- 作用: 处理范围分片场景,即 SQL 条件中包含
-
复合分片算法 (
ComplexKeysShardingAlgorithm
)- 作用: 当分片策略使用了多个分片键时,使用此算法处理包含多个分片键条件的复杂 SQL。
- 核心接口:
doSharding(availableTargetNames, complexKeysShardingValues)
:根据传入的多个分片键的值(精确值或范围值)计算目标数据源或表集合。complexKeysShardingValues
:一个 Map,Key 是分片键名,Value 是该分片键对应的ShardingValue
对象(可能是精确值列表或值范围)。
- 实现方式: 通常需要自定义实现此接口,编写逻辑处理多个分片键的组合路由。内置无通用实现。
- 示例场景: 同时按
tenant_id
和order_type
分片。查询WHERE tenant_id = 1 AND order_type IN (2, 3)
时,算法需要综合两个条件计算目标表。
三、 分片策略类型 (Sharding Strategy Type)
在配置层面,分片策略分为两种,它们都遵循 分片策略 = 分片键 + 分片算法
的公式,但作用域不同:
-
数据库分片策略 (
databaseStrategy
/defaultDatabaseStrategy
)- 定义: 指定数据如何路由到不同的物理数据库 (DataSource) 的规则。
- 配置元素:
shardingColumn(s)
: 库分片键(单个或多个)。shardingAlgorithmName
: 指向一个配置好的分片算法(可以是Standard
、Complex
或ClassBased
类型)。
- 示例 (YAML):
rules: - !SHARDING tables: t_order: # 逻辑表名 actualDataNodes: ds${0..1}.t_order${0..15} # 物理数据节点 ds0.t_order0 ... ds1.t_order15 databaseStrategy: # 数据库分片策略 standard: shardingColumn: user_id shardingAlgorithmName: db_inline_mod shardingAlgorithms: db_inline_mod: type: INLINE props: algorithm-expression: ds${user_id % 2} # 按 user_id % 2 路由到 ds0 或 ds1
-
表分片策略 (
tableStrategy
/defaultTableStrategy
)- 定义: 指定数据在同一个数据库内,如何路由到不同的物理表 (Table) 的规则。仅在分库分表或仅分表时配置。
- 配置元素:
shardingColumn(s)
: 表分片键(单个或多个)。shardingAlgorithmName
: 指向一个配置好的分片算法。
- 示例 (YAML - 接上例):
tables: t_order: # ... actualDataNodes 同上 ... tableStrategy: # 表分片策略 standard: shardingColumn: order_id shardingAlgorithmName: table_inline_mod shardingAlgorithms: # ... db_inline_mod 同上 ... table_inline_mod: type: INLINE props: algorithm-expression: t_order${order_id % 16} # 在同一个库内,按 order_id % 16 路由到 t_order0 到 t_order15
四、 绑定表 (Binding Table) 与广播表 (Broadcast Table)
虽然不是直接的分片策略,但它们与分片策略紧密相关,影响 SQL 路由和执行的效率:
-
绑定表 (Binding Table)
- 定义: 指具有完全相同分库分表规则(即相同的分片键和分片算法)的主表和它的关联子表(如
t_order
和t_order_item
都按order_id
分片)。 - 作用: 避免笛卡尔积关联查询。
- 原理: 当执行
t_order
JOINt_order_item
ONorder_id
时,ShardingSphere-JDBC 知道它们的分片规则一致,只会将关联查询路由到具有相同order_id
后缀的物理表上进行(如t_order_0
JOINt_order_item_0
),而不是t_order_0
JOIN 所有t_order_item_x
。 - 配置 (YAML):
rules: - !SHARDING bindingTables: - t_order, t_order_item # 声明 t_order 和 t_order_item 是绑定表关系
- 定义: 指具有完全相同分库分表规则(即相同的分片键和分片算法)的主表和它的关联子表(如
-
广播表 (Broadcast Table)
- 定义: 指存在于所有分片库中、数据量小且更新不频繁的表(如字典表
t_dict
、配置表t_config
)。 - 作用: 简化关联查询,避免跨库 JOIN。
- 原理: 对广播表的任何操作(INSERT/UPDATE/DELETE/SELECT)都会被广播到所有分片库上执行。查询时,与广播表 JOIN 的操作可以在本地库内完成。
- 配置 (YAML):
rules: - !SHARDING broadcastTables: - t_config # 声明 t_config 是广播表 - t_region
- 定义: 指存在于所有分片库中、数据量小且更新不频繁的表(如字典表
五、 配置详解 (YAML 示例)
一个包含分库分表策略、绑定表、广播表的较完整配置示例:
rules:
- !SHARDING
# 1. 数据节点定义 (物理库.物理表)
tables:
t_order: # 逻辑表名
actualDataNodes: ds${0..1}.t_order${0..15} # 物理节点: ds0.t_order0, ds0.t_order1 ... ds1.t_order15
# 2. 数据库分片策略 (路由到哪个库)
databaseStrategy:
standard:
shardingColumn: user_id # 库分片键
shardingAlgorithmName: db_inline_mod # 使用的库分片算法
# 3. 表分片策略 (在同一个库内路由到哪个表)
tableStrategy:
standard:
shardingColumn: order_id # 表分片键
shardingAlgorithmName: table_inline_mod # 使用的表分片算法
t_order_item: # 子表
actualDataNodes: ds${0..1}.t_order_item${0..15}
databaseStrategy:
standard:
shardingColumn: user_id # 必须与主表 t_order 的库分片键和算法一致
shardingAlgorithmName: db_inline_mod
tableStrategy:
standard:
shardingColumn: order_id # 必须与主表 t_order 的表分片键和算法一致
shardingAlgorithmName: table_inline_mod
# 4. 绑定表声明 (优化关联查询)
bindingTables:
- t_order, t_order_item # 声明绑定关系
# 5. 广播表声明
broadcastTables:
- t_config # 配置表
- t_region # 地区表
# 6. 分片算法定义
shardingAlgorithms:
db_inline_mod: # 库分片算法 (INLINE 表达式)
type: INLINE
props:
algorithm-expression: ds${user_id % 2} # 根据 user_id 模 2 选择库 (ds0/ds1)
table_inline_mod: # 表分片算法 (INLINE 表达式)
type: INLINE
props:
algorithm-expression: t_order${order_id % 16} # 根据 order_id 模 16 选择表 (t_order0 - t_order15)
# 7. 默认策略 (可选,如果表未单独配置策略则使用此默认)
defaultDatabaseStrategy:
none: # 例如,所有表都在同一个库,则不需要分库策略
defaultTableStrategy:
none:
六、 选择分片策略的关键考虑因素
- 数据分布均匀性: 确保分片键值分布均匀,避免数据倾斜(某些库/表数据量巨大,某些空闲)。
- 查询模式:
- 高频查询条件是否包含分片键?包含则能精准路由。
- 是否需要范围查询?需要则选择支持范围分片的算法。
- 是否有复杂多条件查询?可能需要复合分片或Hint。
- 业务增长: 分片策略是否易于扩容?取模算法扩容较麻烦(需要数据迁移),范围、时间分片扩容相对容易。
- 事务与关联查询:
- 跨分片事务复杂,尽量让相关数据在同一分片(如绑定表)。
- 避免非分片键上的复杂 JOIN 和 GROUP BY,性能差。
- 分片键选择: 是策略成功的基础。选择基数大、业务常用、分布均匀的字段。
七、 常见问题与陷阱
- 分片键不在查询条件中: 导致全库全表路由 (
SELECT * FROM t_order
),性能灾难!务必确保核心查询包含分片键。 - 分片键值不均衡: 导致数据倾斜。需选择分布均匀的键或调整算法(如一致性哈希)。
- 扩容麻烦(取模算法):
% 2
扩容到% 4
需要数据迁移。考虑范围分片或一致性哈希。 - 跨分片复杂查询性能差: 如非分片键上的
ORDER BY ... LIMIT
、DISTINCT
、GROUP BY
、跨分片 JOIN。需业务设计规避或接受性能损耗。 - 分布式事务: 跨分片更新需要分布式事务支持(XA/Seata等),增加复杂度和性能开销。
- 分布式主键: 需使用分布式ID生成器(如Snowflake, UUID)代替数据库自增ID,避免冲突。
- 分页查询陷阱:
LIMIT 10 OFFSET 100
在分片环境下含义不同(每个分片取前110条,再归并取10条),效率低且结果可能不稳定。尽量使用连续翻页(WHERE id > last_max_id LIMIT 10
)。
总结: ShardingSphere-JDBC 的分片策略是其最核心的配置。理解 分片键 + 分片算法
的构成,熟练掌握 Standard
、Range
、Complex
算法的应用场景,合理配置 databaseStrategy
和 tableStrategy
,并善用 绑定表
和 广播表
优化查询,是构建高效、可扩展分库分表系统的关键。务必结合业务场景谨慎选择分片键和算法,并充分意识到分片带来的查询限制和挑战。