# 基础配置实战

作者:Ethan.Yang
博客:https://blog.ethanyang.cn (opens new window)
代码参考:[https://github.com/YangYingmeng/learning_shardingJDBC)


# 一、插入数据

# 1.插入绑定表(主表 + 明细表)

/**
 * 插入 订单 + 订单项(绑定表)
 */
@Test
public void testSaveProductOrderAndItem() {

    Random random = new Random();

    for (int i = 0; i < 20; i++) {
        // 1️⃣ 先插入订单主表
        Long userId = (long) random.nextInt(50);

        ProductOrderDO order = new ProductOrderDO();
        order.setCreateTime(new Date());
        order.setNickname("自定义水平分库分表 i=" + i);
        order.setOutTradeNo(UUID.randomUUID().toString().replace("-", ""));
        order.setPayAmount(100.00);
        order.setState("PAY");
        order.setUserId(userId);

        // 这里不要自己 setId,让 ShardingSphere 生成
        productOrderMapper.insert(order);

        // 拿到 ShardingSphere 生成的订单ID
        Long orderId = order.getId();
        System.out.println("生成订单ID = " + orderId + ",用户ID = " + userId);

        // 2️⃣ 再插入订单项(绑定表)
        for (int j = 0; j < 3; j++) {
            ProductOrderItemDO item = new ProductOrderItemDO();
            item.setProductOrderId(orderId);     // 关键:绑定用这个字段分片
            item.setProductId(1000L + j);
            item.setProductName("测试商品-" + j);
            item.setBuyNum(1 + j);
            item.setUserId(userId);              // 必须和订单 userId 一致(用于分库)

            productOrderItemMapper.insert(item);
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

# 分片规则回顾

当前分片规则为:

  • 分库策略: ds$->{user_id % 2}
  • 订单分表: product_order_$->{id % 2}
  • 订单项分表: product_order_item_$->{product_order_id % 2}

假设某一次循环中:

userId   = 41
orderId  = 1996197413044035585
1
2

则路由过程如下:

  1. 分库计算

    41 % 2 = 1  →  路由到 ds1
    
    1
  2. 订单分表

    1996197413044035585 % 2 = 1  →  product_order_1
    
    1
  3. 订单项分表

    product_order_id = 1996197413044035585
    1996197413044035585 % 2 = 1  →  product_order_item_1
    
    1
    2

最终结果:

  • 订单主表:ds1.product_order_1
  • 订单项表:ds1.product_order_item_1

绑定表的核心价值: 绑定后多张表使用同一套分库分表策略,同一笔业务数据(订单 + 明细)一定落在同一个库、同一个分片上,从而可以高效 JOIN,不需要跨库跨表 JOIN。

# 逻辑 SQL / 真实 SQL 对比

逻辑 SQL(代码中看到的):

INSERT INTO product_order  
( id, out_trade_no, state, create_time, pay_amount, nickname, user_id )
VALUES  ( ?, ?, ?, ?, ?, ?, ? )
1
2
3

开启日志(sql.show: true)后,打印的真实 SQL:

ds1 ::: 
INSERT INTO product_order_1  
( id, out_trade_no, state, create_time, pay_amount, nickname, user_id )  
VALUES  
(1996197413044035585, 7256fa9d3d5b4bcfb9198bfdf7011eb4, PAY, 2025-12-03 20:38:45.433, 100.0, 自定义水平分库分表 i=0, 41)
1
2
3
4
5

可以验证三件事:

  1. 逻辑表 product_order → 已改写为物理表 product_order_1
  2. 数据源正确路由到了 ds1
  3. Snowflake 生成的 ID 已回填到实体,并参与了分表计算

# 2. 插入广播表

/**
 * 插入广播表 ad_config
 */
@Test
public void testSaveAdConfig() {

    for (int i = 0; i < 5; i++) {
        AdConfigDO config = new AdConfigDO();
        config.setConfigKey("config_key_" + i);
        config.setConfigValue("config_value_" + i);
        config.setType("SYSTEM");

        adConfigMapper.insert(config);

        System.out.println("生成 ad_config ID = " + config.getId());
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 结果说明

广播表的特点是:每个库都有一份,结构和数据必须保持一致

所以这段代码逻辑上只插入一次,但 ShardingSphere 会自动向:

  • ds0.ad_config
  • ds1.ad_config

各插入一条完全相同的数据。

你只维护一份逻辑数据,框架自动帮你做“全库同步”。

# 逻辑 SQL / 真实 SQL 对比

逻辑 SQL:

INSERT INTO ad_config  
( id, config_key, config_value, type)
VALUES  ( ?, ?, ?, ?)
1
2
3

真实 SQL:

ds0 ::: INSERT INTO ad_config  ( id,config_key,config_value,type )
VALUES  (?, ?, ?, ?) ::: [1996211802992246785, config_key_0, config_value_0, SYSTEM]

ds1 ::: INSERT INTO ad_config  ( id,config_key,config_value,type )
VALUES  (?, ?, ?, ?) ::: [1996211802992246785, config_key_0, config_value_0, SYSTEM]
1
2
3
4
5

# 二、查询数据

# 1. 广播表查询

测试案例

/**
 * 查询广播表 ad_config
 */
@Test
public void testListAdConfig() {

    System.out.println(adConfigMapper.listAdConfig());
}
1
2
3
4
5
6
7
8
# 逻辑 / 真实SQL对比
Logic SQL: select * from ad_config
Actual SQL: ds1 ::: select * from ad_config
1
2

说明:

  • 广播表的数据在各个库中是完全一致的;
  • 对于简单的单表查询,ShardingSphere 会采用 Unicast Route(单播路由)
    • 只从其中一个数据源(这里是 ds1)查询一次即可
    • 无需对所有库做 UNION 合并

只有在广播表参与分片表的 JOIN 等复杂场景时,才会根据主表的路由一起参与本库查询,而不是每个库都查一遍。

# 2. 绑定表查询

# 单表查询绑定表的主表

测试案例

/**
 * 查询绑定表单张 product_order
 */
@Test
public void testListProductOrder() {

    System.out.println(productOrderMapper.listProductOrder());
}
1
2
3
4
5
6
7
8
# 逻辑 / 真实SQL对比
Logic SQL: select * from product_order

Actual SQL: ds0 ::: select * from product_order_0
Actual SQL: ds0 ::: select * from product_order_1
Actual SQL: ds1 ::: select * from product_order_0
Actual SQL: ds1 ::: select * from product_order_1
1
2
3
4
5
6

这里要特别强调一个点:

❗️并不是因为是绑定表才全库全表查,而是因为这条 SQL 没有带任何分片条件(比如 user_id / id),无法做精准路由,所以退化成了“全路由(全库 + 全分片)”。

也就是说:

  • 如果你写的是:select * from product_order没有 where 条件
    • 那么 ShardingSphere 不知道要查哪个库、哪张分表
    • 于是只好把所有库、所有分片都查一遍后再做结果合并
  • 如果你写的是:select * from product_order where user_id = ?
    • 那就会按照 user_id 精准路由,只查 一个库 两张分表(按 id 分表决定)

绑定表只解决“多表路由一致性问题”,不影响单表是否全路由。

# 关联查询

测试案例

/**
 * 查询绑定表 product_order product_order_item
 */
@Test
public void testListProductOrderDetail() {

    System.out.println(productOrderMapper.listProductOrderDetail());
}
1
2
3
4
5
6
7
8
# 逻辑 / 真实SQL对比
Logic SQL: select * from product_order o 
		   left join product_order_item i on o.id=i.product_order_id


Actual SQL: ds0 ::: select * from product_order_0 o 
			  		left join product_order_item_0 i on o.id=i.product_order_id
Actual SQL: ds0 ::: select * from product_order_1 o 
			  		left join product_order_item_1 i on o.id=i.product_order_id
Actual SQL: ds1 ::: select * from product_order_0 o 
			  		left join product_order_item_0 i on o.id=i.product_order_id		
Actual SQL: ds1 ::: select * from product_order_1 o 
			  		left join product_order_item_1 i on o.id=i.product_order_id                    
1
2
3
4
5
6
7
8
9
10
11
12

说明两点:

  1. 依然因为没有带分片键条件(如 user_idid),所以是 全库全分片路由
  2. 由于 product_orderproduct_order_item 是绑定表:
    • 在同一个库中,product_order_0 只会跟 product_order_item_0 JOIN
    • product_order_1 只会跟 product_order_item_1 JOIN
    • 避免了跨分片 JOIN,这是绑定表的价值所在。

✅ 推荐写法: 尽量在 WHERE 中带上分片键,例如:

select * 
from product_order o 
left join product_order_item i on o.id = i.product_order_id
where o.user_id = 41;
1
2
3
4

这样就可以精准路由到单库单分片,避免全库扫描。

# 三、修改 / 删除数据的建议

ShardingSphere 对多表 UPDATE / DELETE 支持有限,尤其是 MySQL 的 UPDATE ... JOIN ... 这种 Multiple-Table 语法,在很多版本中是直接不支持的。

建议:

  1. 尽量使用单表 UPDATE / DELETE
    • 复杂场景改为“两步走”:先查询 ID 列表,再按 ID 单表更新 / 删除。
  2. WHERE 条件一定要带真实分片键
    • 比如:user_idorder_idproduct_order_idid
    • 否则很容易退化为全库全分片广播更新 / 删除,存在严重风险。
  3. 绑定表可以保证:
    • 当你对主表做精准路由时,
    • JOIN 的从表也能路由到同一库、同一分片
    • 绑定表本身并不能“补齐分片键”,WHERE 条件里仍然需要显式带上分片键。

✅ 总结一句话:

  • 查询/修改绑定表时:一定要带分片键,走精准路由;
  • 修改/删除时:尽量拆成单表操作,避免使用 UPDATE ... JOIN ... 这种多表 DML。