订单表多维分库分表解决方案

一、背景

订单分库分表不同于其他业务表分库分表,订单表的读写场景复杂,一般有买家维度、卖家维度、订单号维度3个主要维度。多读写维度情况下无论采取哪种维度做分库分表,对另外两种维度的查询性能来说,基本都是灾难。而在电商项目中,每个订单从创建到终态往往都会经历上百个业务环节,任何一个环节出问题就会导致整单数据异常。所以交易系统可能不是技术难度最深的系统,但一定是业务复杂度最高、稳定性要求最高的系统

二、订单单表面临的问题

订单单表数据量大了之后一般会面临以下问题:

  • 单表过大,SQL RT过高
  • 数据库实例负载过高
  • 单库实例磁盘空间有限

以上都是比较严重的问题,如果不予以解决,将会严重影响机器性能、限制业务发展

应对以上问题一般常用的优化思路如下:

  • 优化SQL
  • 优化索引
  • 优化业务
  • 读写分离
  • 复杂查询借助ES

仅通过以上优化方式,数据量几千万后,业务高峰期还是顶不住,压测也压不上去,这个时候就不得不采取分库分表措施了。

三、拆分类型介绍

3.1 垂直(纵向)拆分

根据业务特性,与拆分微服务的做法相似,将关联度低或数据量巨大的不同表存储在不同的数据库,或者将不经常用或字段长度较大的字段拆分到扩展表中。

垂直(纵向)拆分

垂直拆分的优点:

  • 降低业务表之前的耦合度,层次分明
  • 一定程度上提升数据库资源的性能瓶颈

垂直拆分的缺点:

  • 单表数据量过大的问题无法解决
  • 拆分过程对历史业务改造较大

3.2 水平(横向)拆分

将大表按照某个字段采取不同的路由函数分散到多个数据库或多个表中,表字段完全一致,每个表中只包含一部分数据,所有表加起来等于全量数据。

水平(横向)拆分

水平拆分的优点:

  • 有效解决单表数据量过大的问题,提升系统稳定性和负载能力
  • 业务层面改造较小

水平拆分的缺点:

  • 所有业务SQL安全起见必须带上分表键,如果不带分表键查询会扫全表,性能极低
  • 需要自行解决全局唯一ID问题
  • 跨表查询性能较差,只能通过接口聚合方式解决,提升了开发的复杂度

四、常用分片算法

4.1、按时间维度(年/月/日)

比如按年/日分表,同一天的数据都能落在同一张分表,后续订单的查询条件都必须带上时间属性

按时间维度拆分适用场景:

  • 业务跟时间关联度高
  • 同一时间段内业务数据量分布比较均匀
  • 适用于报表、统计系统相关的业务

可行性分析:

按时间分库分表比较难做,由于订单业务逻辑繁多,不可能所有业务都有时间作为划分,即使带上时间属性,业务查询时数据库压力分布也是在很小范围内,并不能减轻实例负载,所以并不适合订单分库分表的场景。时间可以作为后续做冷热分离的条件。

4.2、HASH

HASH函数的算法是简单取模,若分库和分表使用不同拆分键进行HASH时,则根据分库键的键值直接按分库数取模,如果键值是字符串,则字符串会先被换算成哈希值再进行路由计算。若分库和分表都使用同一个拆分键进行HASH时,则根据拆分键的键值按总的分表数取模。

以t_user表为例,拆分成8个库,每个库8张表:


HASH适用场景:

  • 一个分表键够用
  • 适合于需要按用户ID或订单ID进行分库分表的场景
  • 适合于拆分键是数字、字符串类型的场景

可行性分析:

采用HASH函数进行分库分表,考虑到订单会有按买家、按卖家、按订单号3种主要查询场景,就需要分别以买家ID、卖家ID、订单号3个维度去做3个不同的分库分表。这种分库分表方式可行。但是数据就会有3份,存储是需要考虑先按哪个维度、再按哪个维度、最后按什么维度,所以数据存储流程拉长了,出异常的可能性变大,对业务不太友好

4.3、RANGE_HASH

选取两个拆分键,两个拆分键的后N位需确保一致,根据任一拆分键后N位计算哈希值,然后再按分库数取模,完成路由计算。此路由方式需要自行实现分片算法

以t_order表为例,拆分成8个库,每个库8张表:


RANGE_HASH适用场景:

  • 两个拆分键有关联
  • 查询时有其中一个拆分键值的场景

可行性分析:

如果采取RANGE_HASH函数作为分库分表,则最优方案是以订单号和买家id的后N位做分库分表,后续按订单号维度、买家id维度查询都能满足,卖家维度无法查询。但是前提是订单号后几位和买家id要有关联,涉及到订单号改造的过程。

五、拆分工具

ShardingSphere

在拆分工具选择上,这里借助ShardingSphere官网,ShardingSphere是一套开源的分布式数据库解决方案组成的生态圈,它们能提供数据分片、分布式事务、分布式治理等功能。

如果对ShardingSphere原理、使用感兴趣,大家可以去ShardingSphere官网进一步学习

六、订单分库分表

6.1、订单表主要服务的读写场景

  • 根据买家id查询
  • 根据卖家id查询
  • 各个业务环节、下游应用中大量使用的根据订单号读写

6.2、订单表适用的分表方式

通过对订单表读写场景、不同分表函数、改造成本的分析,订单表采用水平拆分适用的分表方式如下:

  • 用HASH函数,就需要以买家、卖家、订单号3个维度拆分成3张逻辑表,读写时根据场景判断走哪个表,数据存储链路较长
  • 用RANGE_HASH函数,以订单号和买家id的后N位做分库分表,能满足订单号和买家id两个维度的查询,卖家id维度无法查询

6.3、最终采取方案

综上,因为订单一般都是由买家发起生成的,所以我们认为优先处理买家数据和订单号数据比较合理,所以我们先采用RANGE_HASH拆分算法按买家id后N位、订单号后N位维度做分库分表,作为买家表逻辑表。再用HASH拆分函数按商家id冗余一份数据,作为卖家表逻辑表

  • 买家库、买家表:下单填充买家表数据,用于以订单号、以买家id查询
  • 卖家库、卖家表:异步冗余一份买家表数据到卖家表,用于根据卖家id查数据
订单买卖家表

6.4、订单号生成

订单号生成规则需要根据买家表分表特性订单号后N位等于买家Id后N位做设计

比如用户id为12345678,则用户在下单时生成的单号为:xxxxxxxxx345678,单号前几位可以根据公司自己规则设定,但是要注意不能重复

6.5、分表数的确定

一般单表数据量控制在100W到5000W之间比较合理,可以根据预估未来2-3年数据的增长预期计算出合适的分表数

6.6、历史订单处理

历史订单没有按照订单号生成规则来生成,但是历史订单也会有买家id,所以拆分函数配置时,特意将买家id后N位作为前置条件,历史订单会以买家id后N位作为分表键落库。

给历史订单建一个索引表,只保留订单号和买家id关系

CREATE TABLE `order_history_index` (
`orderId` bigint(20) unsigned NOT NULL COMMENT '订单ID',
`buyerUserId` bigint(20) unsigned NOT NULL COMMENT '买家的userId'
PRIMARY KEY (`orderId`)
) 

历史订单根据订单号查询时先过索引表取到买家id,再查买家表取到具体数据。

注意:

  • 还可以在订单号里面添加了卖家id末2位、买家id末2位。
  • 而分库、分表的规则可以设计成,买家库是按照卖家id末尾2位数分,卖家库是按照卖家id末尾两位分。
  • 所以,只要从订单号里面拿到了这些数字信息,就知道在哪个库,哪个表了。

这种办法,与微信的红包订单号是类似的,末尾三位数包含了库信息、表信息。

  • 如果是按照用户id的后4位数取模分散订单数据。那么订单号的生成,可以在后面加上用户id的后4位数。
  • 那么,虽然是按照用户id来对订单表分库分表的。其实可以直接根据订单号,就知道这个订单在哪个库哪个表了。
  • 如果是B2B2C系统,涉及到卖家和买家。那么可以把卖家和买家的id后面4位都加进去。不过是不是订单号有些偏长。

七、总结

按照上述方式对订单分库分表后,业务里常用的查订单表sql,通过编码层面区分,路由到各个库的买家表、卖家表里,有效解决单表数据量过大的问题,提升系统稳定性和负载能力。

当然肯定还有个别复杂查询和分表键沾不上边的,这些业务场景对实时性要求不高的,可以走ES或者离线库去查询

参考:
https://maimai.cn/article/detail?fid=1639183809&efid=I5KVAmjkHZ_UPeF8wcR6gQ

https://www.cnblogs.com/wangtao_20/p/7115962.html

https://www.likecs.com/show-737990.html

https://my.oschina.net/vivotech/blog/5286769

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,084评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,623评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,450评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,322评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,370评论 6 390
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,274评论 1 300
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,126评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,980评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,414评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,599评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,773评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,470评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,080评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,713评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,852评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,865评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,689评论 2 354

推荐阅读更多精彩内容