跳到主要内容

虚拟分片查询一致性分析

如果小伙伴对于 为什么通过 订单编号(OrderNumber)以及 用户id(userId)在虚拟分片路由表查询后,能够定位到相同的物理库和物理表 还有疑惑的话,可以详细查看此篇章节的讲解

一、观察到的现象

订单编号查询:

shardingKey = 1978005150261182474
→ 虚拟分片266 → 库0.表2

用户ID查询:

shardingKey = 1136344580112367618
→ 虚拟分片258 → 库0.表2

现象:

虚拟分片ID不同(266 vs 258),但最终路由到相同位置(库0.表2)!

二、真实数据计算验证

订单号:1978005150261182474

步骤1:提取基因位(后3位)

1978005150261182474 & 0b111 = 2 (二进制:0b10)

步骤2:解析基因位

  • 表索引(后2位):2
  • 库索引(第3位):0

步骤3:计算物理分片索引

physicalShardIndex = 0 × 4 + 2 = 2

步骤4:计算虚拟偏移

1978005150261182474 % 128 = 10

步骤5:计算虚拟分片ID

logicalShardId = 2 × 128 + 10 = 266

用户ID:1136344580112367618

步骤1:提取基因位(后3位)

1136344580112367618 & 0b111 = 2 (二进制:0b10)

步骤2:解析基因位

  • 表索引(后2位):2
  • 库索引(第3位):0

步骤3:计算物理分片索引

physicalShardIndex = 0 × 4 + 2 = 2

步骤4:计算虚拟偏移

1136344580112367618 % 128 = 2

步骤5:计算虚拟分片ID

logicalShardId = 2 × 128 + 2 = 258

关键对比

维度订单号用户ID是否相同
基因位(后3位)22✅ 相同
物理分片索引22✅ 相同
虚拟偏移102❌ 不同
虚拟分片ID266258❌ 不同
虚拟分片范围256-383256-383✅ 相同
最终路由库0.表2库0.表2✅ 相同

三、核心机制解析

3.1 为什么 physicalShardIndex 相同?

关键:订单号生成时嵌入了用户ID的基因位!

回顾 SnowflakeIdGenerator.getOrderNumber() 方法:

public synchronized long getOrderNumber(long userId, 
long tableCount,
long databaseCount) {
//pro项目中有详细的代码和注释
}

结果:

userId的后3位 = 订单号的后3位

验证:
userId: 1136344580112367618 & 0b111 = 2
orderNumber: 1978005150261182474 & 0b111 = 2
✅ 相同!

3.2 为什么虚拟分片ID不同但路由相同?

物理分片2对应的虚拟分片范围:256 - 383

路由表映射:

┌─────────────────┬──────────────────┬──────────────────┐
│ 虚拟分片ID │ 物理库表 │ 说明 │
├─────────────────┼──────────────────┼──────────────────┤
│ 256 │ db_0.table_2 │ │
│ 257 │ db_0.table_2 │ │
│ 258 │ db_0.table_2 │ ← 用户ID路由到这里│
│ ... │ db_0.table_2 │ │
│ 266 │ db_0.table_2 │ ← 订单号路由到这里│
│ ... │ db_0.table_2 │ │
│ 383 │ db_0.table_2 │ │
└─────────────────┴──────────────────┴──────────────────┘

关键:虚拟分片 256-383 的所有ID都映射到同一个物理表!

三层保障机制

第1层保障:基因法
├─ 订单号生成时嵌入userId的基因位(后3位)
└─ 结果:订单号和userId的基因位相同 ✅

第2层保障:物理分片索引
├─ 基因位相同 → physicalShardIndex相同
└─ 结果:都路由到物理分片2 ✅

第3层保障:虚拟分片范围
├─ physicalShardIndex相同 → 虚拟分片在同一个128范围内
├─ 物理分片2 → 虚拟分片256-383
└─ 结果:266和258都在256-383范围内 ✅

最终保障:路由表映射
├─ 虚拟分片256-383全部映射到同一个物理表
└─ 结果:都路由到 damai_order_0.d_order_2 ✅

3.3 为什么必须整体迁移128个虚拟分片?

❌ 错误示例(只迁移部分虚拟分片)

虚拟分片258 → 迁移到 db_0.table_6
虚拟分片266 → 保留在 db_0.table_2

结果:
用户ID查询 → 路由到 table_6 ✗
订单号查询 → 路由到 table_2 ✗
查询不一致!数据找不到!

✅ 正确示例(整体迁移128个虚拟分片)

虚拟分片256-383 → 全部迁移到 db_0.table_6

结果:
用户ID查询 → 路由到 table_6 ✓
订单号查询 → 路由到 table_6 ✓
查询一致!数据都在table_6!

3.4 🔑 核心问题:为什么 266 和 258 都在 [256, 383] 范围内?

💡 最核心的本质(一句话说清楚)

关键就是这一行代码

int virtualOffset = (int)(Math.abs(shardingKey) % virtualShardsPerPhysical);
// ↑
// 对128取模