建议收藏 聊聊分库分表后非Sharding Key查询的三种方案~( 二 )

这样保证了同一个用户创建的所有订单都落到了同一个分片上,order_id的最后4个bit都相同,于是:

  • 通过user_id %16 能够定位到分片
  • 通过order_id % 16也能定位到分片
不好理解的话,可以看下面这段代码:
@Testpublic void modIdTest(){long userID = 20160169L;//分片数量int shardNum = 16;String gen = getGen(userID, shardNum);log.info("userID:{}的基因为:{}",userID,gen);long snowId = IdWorker.getId(Order.class);log.info("雪花算法生成的订单ID为{}",snowId);Long orderId = buildGenId(snowId,gen);log.info("基因转换后的订单ID为{}",orderId);Assert.assertEquals(orderId % shardNum , userID % shardNum);}运行结果如下:
建议收藏 聊聊分库分表后非Sharding Key查询的三种方案~

文章插图
 
原始订单ID为1595662702879973377,通过基因转换后ID变成了1595662702879973385,对于用户id 和 新生成的订单id对其取模结果一样 。
上面那种做法是基因替换,替换掉订单id的分片基因 。下面这种做法就更显直接 。
将订单表 orders 的主键设计为一个字符串,这个字符串中最后一部分包含分片键的信息,如:
order_id = string(order_id + user_id)那么这时如果根据 order_id 进行查询:
SELECT * FROM T_ORDERWHERE order_id = '1595662702879973377-20160169';由于字段 order_id 的设计中直接包含了分片键信息,所以我们可以直接通过分片键部分直接定位到分片上 。
同样地,在插入时,由于可以知道插入时 user_id 对应的值,所以只要在业务层做一次字符的拼接,然后再插入数据库就行了 。
这样的实现方式较冗余表和索引表的设计来说,效率更高,查询时可以直接定位到数据对应的分片信息,只需 1 次查询就能获取想要的结果 。
这样实现的缺点是,主键值会变大一些,存储也会相应变大 。但是只要主键值是有序的,插入的性能就不会变差 。而通过在主键值中保存分片信息,却可以大大提升后续的查询效率,这样空间换时间的设计,总体上看是非常值得的 。
实际上淘宝的订单号也是这样构建的
建议收藏 聊聊分库分表后非Sharding Key查询的三种方案~

文章插图
 
上图是我的淘宝订单信息,可以看到,订单号的最后 6 位都是 607041,所以可以大概率推测出:
  • 淘宝订单表的分片键是用户 ID;
  • 淘宝订单表,订单表的主键包含用户 ID,也就是分片信息 。这样通过订单号进行查询,可以获得分片信息,从而查询 1 个分片就能得到最终的结果 。
小结分库分表后需要遵循一个基本原则:所有的查询尽量带上sharding key,有时候业务需要根据技术限制进行妥协,那种既要...又要...就是在耍流氓 。
当然有些业务场景确实没办法避免,对于非sharding key的查询可以参考上面三种方案实现,不过实际上只能算两种 。




推荐阅读