订单表的分库分表方案设计(大数据)
订单表的分库分表方案设计
在业界的大数据场景下,订单表的分库分表(Sharding)设计通常主要有两类思路:按照订单号切分或按照用户 ID 切分。以下是对这两种方案的详细分析、优缺点对比以及常见问题的解决方案。
主流分库分表方案
方案一:按照订单号 Hash 分散
该方案将订单号视为字符串,通过 Hash 算法将数据分散到多个数据库服务器中。
- 路由规则:具体存储到哪个库、哪张表,通常由订单号中的数字特征决定。
- 典型案例:例如微信红包的订单分库分表,是对订单号进行 Hash 计算(非简单的取模或取整),以实现数据的均匀分散。订单号末尾的 3 位数字往往包含了库名和表名的信息。
查询挑战:
- 如果需要查询某用户的所有订单,由于数据是根据订单号分散的,该用户的订单可能分布在多个库、多张表中。
- 若全库全表扫描,效率极低。
- 对比:即便是按照用户 ID(UID)切分,查询特定订单号时也会面临类似问题(不知道数据在哪个库)。
方案二:按照用户 ID 分散
以用户 ID(UID)作为切分键,业界使用较为普遍。主要有两种思路:
范围切分:例如 0-2000 万 UID 对应库 A,2000 万 -4000 万 UID 对应库 B。
- 缺点:容易出现数据热点。若某个范围内的用户下单量特别大,会导致对应库压力过高,而其他库闲置。
取模运算(推荐):使用
UID % N的方式。- 优点:处理简单,数据能均匀分散到多个库,不易出现单库性能瓶颈。
- 缺点:扩容麻烦。扩容通常需要进行数据迁移。
- 扩容策略:为了减少迁移量,一般以倍数形式增加库数量(例如从 8 库扩容到 16 库,再到 32 库)。这种方式虽不能彻底避免迁移,但能显著减少迁移数据量,且一次扩容可支撑较长时间的业务增长。
基于用户 ID 分片的详细设计
以下以“按照用户 ID 取模”为例,分析具体的库表定位规则。
1. 路由算法逻辑
假设使用用户 ID 末尾 4 位数字进行计算:
库名称定位:
用户 ID 末尾 4 位 % 32Mod表示取余数。例如15 % 4 = 3。- 余数范围 0-31,对应 32 个数据库实例。
- 库名示例:
order_db_0至order_db_31。
表名称定位:
(用户 ID 末尾 4 位 / 32) % 32Dev表示取整数商(向下取整)。例如15 / 4 = 3.75,取整为3。- 代码表示:
floor(15/4)。 - 表名示例:
order_tb_0至order_tb_31。每个库内包含 32 张表。
2. 容量估算
按照上述规则:32 个库 * 32 张表 = 1024 张表。
- 若单表数据量控制在 1000 万至 5000 万行,理论上可支撑海量数据。
- 若希望表数量更少,可调整取模的基数(如将 32 改小)。
查询场景与挑战
方案一的查询问题
若按照订单号分散数据,查询某用户的所有订单时,必须进行跨库、跨表查询,效率低下,通常不可行。
方案二的查询问题
若按照用户 ID 分散数据,虽然查询用户订单列表很方便,但在实际应用中会面临以下高频查询需求:
- 按订单号查询:前台或后台输入订单号查询详情(Select)。
- 按订单号更新:修改订单状态(Update)。
由于数据是按用户 ID 分散的,仅凭订单号无法直接定位到具体的库和表。若全库扫描,性能无法接受。
解决方案:建立索引映射表
没有一种技术方案是十全十美的。针对“按用户 ID 分片后无法通过订单号查询”的问题,常见的解决思路是建立订单号与用户 ID 的索引关系表。
1. 核心思路
- 映射表设计:建立一张
order_user_idx表,存储两项核心数据:订单号、用户 ID。 查询流程:
- 输入订单号,查询索引表获取用户 ID。
- 根据用户 ID 利用路由算法定位到具体的库和表。
- 查询订单详情。
- 写入流程:创建订单时,需同步插入关系记录。为降低用户等待时间,该操作可异步化(如加入消息队列处理)。
2. 索引表的性能优化
随着时间推移,一个用户的订单量可能达到几百甚至更多,索引表本身也可能成为瓶颈。
- 索引表分片:
order_user_idx表本身也需要分库分表。建议直接根据订单号取模进行分片。 缓存策略:
- 订单与用户的绑定关系创建后通常不会变更。
- 可将该关系表放入内存缓存(如 Redis)。
- 查询时优先走缓存,未命中再查数据库,以提升速度。
复杂业务场景思考
思考一:B2B 平台的分片键选择
在 B2C 平台,卖家通常是平台自身。但在 B2B 平台,买家和卖家均需查看订单。
- 按买家 ID 分片:卖家查询自己的所有订单时,因商品被多个买家购买,订单分散在不同库表,导致跨库查询性能低下。
- 按卖家 ID 分片:买家在多个店铺下单,订单同样分散,买家查询自身订单时性能低下。
行业实践(如淘宝):
采用数据冗余方案,拆分买家库和卖家库。
- 买家库:按买家 ID 分库分表。
- 卖家库:按卖家 ID 分库分表。
- 同步机制:下单时写两份数据。先写入买家库,再通过消息中间件异步同步到卖家库。状态变更时同样通过异步消息通知对方库更新。
思考二:订单号中嵌入分片信息
是否可以直接按订单号分库分表?
若订单分散在不同库表,用户查询历史订单列表(分页)会非常麻烦。
优化方案:
在生成订单号时嵌入分片信息。
- 案例:淘宝订单号中包含卖家 ID 末 2 位、买家 ID 末 2 位。微信红包订单号末尾 3 位包含库表信息。
- 优势:解析订单号即可直接定位库表,无需额外计算或查表。
- 实现:若按用户 ID 后 4 位取模分片,可在订单号生成时追加用户 ID 后 4 位。B2B 场景下可同时包含买卖双方 ID 特征(需注意订单号长度)。
思考三:按时间分表
例如每月一张表或每年一张表。业界较少单独使用此方案切分订单数据,主要存在以下问题:
- 查询困难:查询用户所有订单需跨库跨表。折中方案是限制查询范围(如仅查近一年)。
- 写入热点:若按月分表,当月订单量暴涨会导致单表写入瓶颈,影响整个系统交易。
原则:优秀的分表方案应尽量将写入数据分散到多个表,以达到分流效果,提高系统并发能力。
总结与建议
水平分库分表会显著增加开发和维护工作量。切分后会产生新的查询问题,需要额外的代码逻辑和中间表支持。
- 建议:若业务未达到瓶颈,尽量不要过早进行水平分表。优先通过提升硬件配置(垂直扩展)扛住压力。
- 现状:许多大型企业(如银行、运营商)早期使用高性能 Oracle 搭配小型机服务器,单表数据量可达十多亿。小型机性能强劲但成本高昂。
- 重构时机:当交易量达到上亿规模,且公司具备足够经济实力时,再聘请经验丰富的技术团队进行架构重构。
说明:本文提到的“小型机 + Oracle"方案属于传统集中式架构的典型代表。随着云原生技术的发展,现代架构更多倾向于使用分布式数据库(如 TiDB、OceanBase)或云厂商提供的分库分表中间件(如 ShardingSphere)来解决海量数据存储问题,成本与扩展性更具优势。
版权声明:本文为原创文章,版权归 戴老师的博客 所有,转载请联系博主获得授权。
本文地址:https://1diff.fun/archives/ding-dan-biao-de-fen-ku-fen-biao-fang-an-she-ji--da-shu-ju.html
如果对本文有什么问题或疑问都可以在评论区留言,我看到后会尽量解答。