虚拟分片查询一致性分析
如果小伙伴对于 为什么通过 订单编号(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位) | 2 | 2 | ✅ 相同 |
| 物理分片索引 | 2 | 2 | ✅ 相同 |
| 虚拟偏移 | 10 | 2 | ❌ 不同 |
| 虚拟分片ID | 266 | 258 | ❌ 不同 |
| 虚拟分片范围 | 256-383 | 256-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取模