分库分表的设计思路
选择分片键(Sharding Key)
原则:高基数、业务相关性、分布均匀,防止数据倾斜
常见分片键:
- 用户ID:适用于用户中心化业务(如电商订单、社交动态)。
- 租户ID:多租户 SaaS 系统常用。
- 时间范围:日志、监控数据按时间分片(如按月分表)。
- 订单ID
要防止数据倾斜的问题发生,例如用公司Id做分片,有的公司大,订单多,有的公司小,订单少。
确定分片策略
- 哈希分片:数据分布均匀的场景(如用户ID),优点是实现简单、比较均匀,缺点是无法扩展,数据扩容时需数据迁移;跨分片查询会很麻烦。
- 范围分片:按时间或有序ID查询的场景,优点是:范围查询高效,缺点是:尾部热点风险,最新的数据都在尾部。
- 一致性哈希:动态扩容需求高的场景,优点是:扩容时数据迁移量少,缺点是:实现复杂度较高。
路由规则设计
- 客户端路由:在应用层计算分片位置(如通过分片键的哈希值)。
- 代理中间件:使用 ShardingSphere、MyCAT 等中间件自动路由。
分库分表的跨库关联查询要怎么处理
分库分表后,遇到不按分表键查询的场景。例如:按用户Id(user_id)分了64张表,现在要根据订单号order_id查询,需要遍历64张表聚合结果。针对这种,我们有以下的一些策略:
1.联合主键法
联合主键法其实是利用了分片的主键。
联合主键法的原理出自一个理论:对一个数取余2的n次方,那么余数就是这个数的二进制的最后n位数。
例如根据用户id:user_id,进行哈希取模进行分片。分16片,这样每一片其实就对应一个4位的2进制数,例如:1010,就是所有user_id转为二进制后,如果后四位是1010,都会分配到同一个片。
现在我们又要根据订单号查询,可以这样设计订单号生成的规则:先使用一种分布式ID生成算法生成二进制的前60位,然后加上user_id的后4位,组成一个64位的二进制数,转为10进制,作为订单号。
这样同一个user_id的所有订单号都在同一个分片了,我们也能单独用order_id知道它在哪个分片了。
优点:
- 性能最好,查询快
- 没有延迟问题
- 强一致性,无需多副本同步,天然保证事务完整性。
缺点:
- 分片键设计复杂
- 有热点风险:若联合主键分布不均(如大V用户),可能会数据倾斜。
- 不灵活,查询模式固定:仅支持基于联合主键字段的查询,无法应对多维查询需求。
2.冗余法
这种思路就比较简单,用空间换时间,就是先有一套按照user_id分片的库表,再有一套按照order_id分片的库表。
优点:
- 性能好,查询快。
- 无需修改分片键,实现简单
缺点:
- 数据冗余存储成本高。
- 数据一致性维护难,可能有数据不一致的问题,有延迟
- 运维复杂度高,清洗数据要洗两张表
- 写入开销:两次写入。
3.异构索引
这个就是冗余法的进阶版,即采用异步机制将原表的每一次创建或更新,都换另一个维度保存一份完整的索引表,拿空间换时间。
思路是:构建一个全局索引表,将关联关系(如 user_id → order_id)单独存储,查询时先查索引表定位分片,再定向查询。
可以这样实现:利用阿里的精卫组件(MySQL数据同步的中间件)实现,精卫中间件的数据同步机制,它是分析mysql binlog ,解析成RowChangeEvent以后,包装成消息通过消息中间件Meta发送消息到消息网关,在作同步数据处理,可以自己定义过滤、转换等等。
比如按用户Id(user_id)分了64张表,插入一条数据时,精卫订阅到binlog,会转发一条消息,将这条数据插入到一张索引表order_index中,这张表记录了user_id和order_id,以及数据对应的分片,索引是订单号(或者也可以根据订单号做分片)。
后续根据订单号查询,就可以先查询 order_index 索引表,拿到分片之后再查询。
优点:
- 灵活支持复杂查询,因为可以有多个索引
- 存储成本低,因为只存索引
- 扩展性强,易维护,因为是索引结构,所以不影响数据本身
缺点:
- 查询性能中等:需先查索引再查数据,增加网络IO(如先查索引表获取分片位置,再查目标分片)。
- 最终一致性风险:索引更新延迟。
- 双写开销:每次写入需同时更新主表和索引表,影响吞吐量。
4.大数据查询
这种就是利用大数据的表,例如es、hive、starrocks等等,将mysql的数据存入这些大数据的数据库中,实时性要差一些,但是能实现一些统计的需求。