Sharding-JDBC 的 SQL Hint 机制详解
今天做项目的时候遇到的一个问题,项目中框架底层对sql进行了分表处理。导致登录的用户对应执行的sql只能查到自己数据库的数据,还有一系列的问题。后来发现有这个么插件还挺好用的,记录一下。
SQL Hint 是 Sharding-JDBC 提供的一种强制路由机制,允许你绕过分片规则直接指定数据应该访问哪个分库或分表。这对于特殊查询场景非常有用。
1. SQL Hint 的基本概念
SQL Hint 是一种特殊的注释语法,或者通过 API 方式,可以:
- 强制指定查询某个特定的分库
- 强制指定查询某个特定的分表
- 临时改变分片策略
2. 使用方式
方式 1:通过 HintManager API(推荐)
try (HintManager hintManager = HintManager.getInstance()) {
// 强制路由到库分片值=1的库
hintManager.setDatabaseShardingValue(1);
// 强制路由到表分片值=0的表
hintManager.setTableShardingValue(0);
// 执行查询
List<Order> orders = orderMapper.selectByUserId(userId);
}
方式 2:通过 SQL 注释(5.0 + 版本支持)
/* ShardingSphere hint: dataSourceName=ds0 */
SELECT * FROM t_order WHERE order_id = 1001;
3. 典型使用场景
场景 1:跨分片数据聚合查询
// 查询所有分片的数据
public List<Order> selectAllOrders() {
List<Order> result = new ArrayList<>();
for (int i = 0; i < 2; i++) { // 假设有2个分库
try (HintManager hintManager = HintManager.getInstance()) {
hintManager.setDatabaseShardingValue(i);
result.addAll(orderMapper.selectAll());
}
}
return result;
}
场景 2:绑定表关联查询
// 确保订单和订单项在同一分库查询
public Order getOrderWithItems(Long orderId, Long userId) {
try (HintManager hintManager = HintManager.getInstance()) {
// 使用相同的分片值确保路由到同一库
hintManager.setDatabaseShardingValue(userId % 2);
hintManager.setTableShardingValue(orderId % 2);
Order order = orderMapper.selectById(orderId);
if (order != null) {
order.setItems(orderItemMapper.selectByOrderId(orderId));
}
return order;
}
}
场景 3:运维管理查询
// 直接在指定分库执行DDL语句
public void createIndexOnShard0() {
try (HintManager hintManager = HintManager.getInstance()) {
hintManager.setDatabaseShardingValue(0);
jdbcTemplate.execute("CREATE INDEX idx_user_id ON t_order(user_id)");
}
}
4. 注意事项
- 作用范围:
- Hint 只在当前线程有效
- 使用 try-with-resources 确保及时清理
- 性能影响:
- 避免在循环中频繁切换 Hint
- 大数据量查询可能造成内存问题
- 与分片规则的关系:
- Hint 的优先级高于配置的分片规则
- 未设置的 ShardingValue 仍会使用分片规则计算
- 版本兼容性:
- 不同版本 Hint 语法可能有差异
- 5.0 + 版本支持更丰富的 Hint 功能
5. 高级用法
强制全库全表扫描
// 查询所有分库分表的数据
public List<Order> searchAllShards(OrderQuery query) {
List<Order> result = new ArrayList<>();
for (int db = 0; db < 2; db++) { // 遍历所有库
for (int table = 0; table < 2; table++) { // 遍历所有表
try (HintManager hintManager = HintManager.getInstance()) {
hintManager.setDatabaseShardingValue(db);
hintManager.setTableShardingValue(table);
result.addAll(orderMapper.search(query));
}
}
}
return result;
}
动态分片策略覆盖
// 临时修改分片策略
public List<Order> selectByCustomSharding(Long userId, int mod) {
try (HintManager hintManager = HintManager.getInstance()) {
// 临时改为按 userId % mod 分片
hintManager.addDatabaseShardingValue("t_order", userId % mod);
hintManager.addTableShardingValue("t_order", userId % mod);
return orderMapper.selectByUserId(userId);
}
}
6. 最佳实践
- 限制使用范围:
- 仅用于管理后台、报表查询等特殊场景
- 避免在核心业务逻辑中频繁使用
- 性能优化:
// 并行查询多个分片
public List<Order> fastSearchAllShards() {
return IntStream.range(0, 2) // 假设2个分库
.parallel()
.mapToObj(db -> {
try (HintManager hintManager = HintManager.getInstance()) {
hintManager.setDatabaseShardingValue(db);
return orderMapper.selectAll();
}
})
.flatMap(List::stream)
.collect(Collectors.toList());
}
- 与其它功能结合:
// Hint + 读写分离
try (HintManager hintManager = HintManager.getInstance()) {
hintManager.setDatabaseShardingValue(1);
hintManager.setReadwriteSplittingAuto(false); // 强制走主库
return orderMapper.selectForUpdate(orderId);
}
SQL Hint 是 Sharding-JDBC 中一个强大的逃生通道,合理使用可以解决很多分片环境下的特殊需求,但要注意不要滥用以免破坏分片设计初衷。