# 电商系统分库分表

# 用户业务
分片键选择:user_id
拆分策略:直接通过 user_id 进行 Hash 或取模。因为用户通常只关心自己的数据,绝大多数请求都带有 user_id,可以直接精准定位到库表
# 商品业务
分片键选择:通常按商户/店铺 ID(merchant_id / shop_id) 或 商品分类 拆分
特别注意:商品表往往更依赖分布式缓存(Redis)和搜索引擎(Elasticsearch)来抗读压力,纯数据库的分库分表反而可以相对保守,因为商品业务读多写少
# 订单业务
订单表是电商系统的“核心”,它面临着双向查询的天然矛盾
买家(用户):需要查看“我的订单列表”(需要按 user_id 查询) 卖家(商家):需要查看“我店铺的订单”(需要按 seller_id 查询)
# 基因法
如果你既想按 buyer_id 分片,又想按 order_id 单笔查询时不走全表扫描,可以使用基因法
原理:在生成 order_id(订单 ID)时,把 buyer_id(买家 ID)的后几位(比如后 4 位,即“基因”)融合成 order_id 的一部分。
效果:当按 buyer_id 查询时,根据其后 4 位正常路由。当按 order_id 查询单笔订单时,提取出 order_id 里的后 4 位基因,依然能精准路由到同一个库表
基因法(Genetics)是有局限性的,它只能完美解决“双维度”问题(比如买家维度 + 订单维度),按 seller_id 依然无法直接进行精准路由,可以采用后面的几种方案来解决
# 数据异构(C端、B端库分离)
这是大型互联网公司(如淘宝、京东)最常用的做法,把数据分2份
买家库:以 buyer_id 为分片键(配合基因法生成 order_id),专门抗 C 端用户的高并发高频读写
卖家库:以 seller_id 为分片键,专门抗 B 端商家的后台管理、订单查询
数据同步:C 端用户下单成功后,系统通过监听买家库的 Binlog(使用 Canal 等工具),异步把数据投递到消息队列,再由消费者解析并写入卖家库
优点:读写完全隔离,B 端的复杂查询绝不影响 C 端的下单性能,两端都极快
缺点:存在微秒到毫秒级的“数据同步延迟”,商家可能需要刷新一下才能看到最新订单;同时服务器成本翻倍
# 异构到 Elasticsearch
如果商家不仅要通过 seller_id 查询,还要根据“商品名称、下单时间、订单状态、物流单号”进行复杂的组合筛选,那么即使建了卖家库也玩转不动
做法:同样是通过 Binlog 异步同步,但这次不往 MySQL 存了,而是把所有订单数据实时同步到 Elasticsearch 中。
查询路由:买家查订单、根据订单 ID 查单条,走 MySQL 基因库。卖家查店铺订单、各种复杂条件筛选走 ES 统揽全局
优点:不仅解决了 seller_id 查询问题,还顺便解决了商家多条件模糊搜索的痛点
缺点:依然存在微小的同步延迟;系统架构变重,需要维护 ES 集群
# 通过映射表
如果你们的业务刚起步,数据量虽然超过了单表瓶颈,但还没到能养得起两套库或 ES 集群的程度(或者不想增加架构复杂度),可以使用映射表
做法:建立一张极其轻量级的映射表 t_seller_buyer_map,字段非常简单,只有两个:seller_id 和 buyer_id(可以再加个 order_id)。
分片键:这张映射表以 seller_id 作为分片键。
查询步骤(拆成两步):商家查订单时,系统先拿着 seller_id 去映射表里查询,找出这个商家下面有哪些 buyer_id。拿到 buyer_id 后,应用层再精准路由到买家库里去查真正的订单详情。
优点:成本低,不需要复杂的分布式数据同步机制
缺点:多了一次数据库网络 IO 交互。如果一个大商家的买家极多,映射表返回的数据量过大,这种方案就会失效
← 高并发系统设计