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. 注意事项

  1. 作用范围
  • Hint 只在当前线程有效
  • 使用 try-with-resources 确保及时清理
  1. 性能影响
  • 避免在循环中频繁切换 Hint
  • 大数据量查询可能造成内存问题
  1. 与分片规则的关系
  • Hint 的优先级高于配置的分片规则
  • 未设置的 ShardingValue 仍会使用分片规则计算
  1. 版本兼容性
  • 不同版本 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. 最佳实践

  1. 限制使用范围
  • 仅用于管理后台、报表查询等特殊场景
  • 避免在核心业务逻辑中频繁使用
  1. 性能优化
// 并行查询多个分片
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());
}
  1. 与其它功能结合
// Hint + 读写分离
try (HintManager hintManager = HintManager.getInstance()) {
    hintManager.setDatabaseShardingValue(1);
    hintManager.setReadwriteSplittingAuto(false);  // 强制走主库
    return orderMapper.selectForUpdate(orderId);
}

SQL Hint 是 Sharding-JDBC 中一个强大的逃生通道,合理使用可以解决很多分片环境下的特殊需求,但要注意不要滥用以免破坏分片设计初衷。