订单表的分库分表方案设计(大数据)

 

 

原创文章,转载注明出处

 

 

一、两种方案分库分表

 

 一般业界,对订单数据的分库分表,笔者了解,有两类思路:按照订单号来切分、按照用户id来切分。

 

方案一、按照订单号来做hash分散订单数据

 

     把订单号看作是一个字符串,做hash,分散到多个服务器去。

     具体到哪个库、哪个表存储数据呢?订单号里面的数字来记录着。

    现在的微信红包。它的订单分库分表,是对订单号进行hash计算。不是什么取模、取整数。这样数据是均匀分散的。

    然后订单号的末尾3个数,里面是包含了库名称、表名称的。

 

     如果要查询某用户的所有订单呢?

 

     由于是根据订单号来分散数据的。他的订单分散在了多个库、多个表中。

 

     总不能去所有的库,所有的表扫描吧。这样效率很低。

 

      其实按照uid切分订单,一样会遇到查询的问题。比如要查询a订单的信息。分库分表的规则是按照uid,都不知道数据在哪个库,无从查。

 

      后续说明解决思路。

 

     一般使用方案二的比较多,一个用户的所有订单,都在一张表里面,那么做分页展示的时候,就容易。

 

 

方案二、按照用户id打散订单数据。

 

 

以uid来切分数据,有两种思路:

 

一种是,某个范围的uid订单到哪些库。0到2千万uid,对应的订单数据到a库、a表。2千万到4千万对应的订单到b库。

 

为什么这种方案用得比较少呢?

 

容易出现瓶颈吗。某个范围内的用户,下单量比较多,那么造成这个库的压力特别大。其他库却没什么压力。

 

 

第二种是,使用uid取模运算。第二种方案业界用得比较多。

 

一方面、处理简单,程序上做取模运算就好了。

另一方面、使用取模的方式,数据比较均匀分散到多个库去了。不容易出现单个库性能瓶颈。

 

但是不好处也有:即要扩容的时候,比较麻烦。就需要迁移数据了。

要扩容的时候,为了减少迁移的数据量,一般扩容是以倍数的形式增加。比如原来是8个库,扩容的时候,就要增加到16个库,再次扩容,就增加到32个库。这样迁移的数据量,就小很多了。这个问题不算很大问题,毕竟一次扩容,可以保证比较长的时间,而且使用倍数增加的方式,已经减少了数据迁移量。

 

下面笔者,分析一下按照用户id取模的方式分库分表。

 

按照用户id作为key来切分订单数据,具体如下:

 

1、 库名称定位:用户id末尾4位 Mod 32。

  Mod表示除以一个数后,取余下的数。比如除以32后,余下8,余数就是8。

  代码符号是用%表示:15%4=3。

 

2、表名称定位:(用户id末尾4位 Dev 32) Mod 32。

 

  Dev表示除以一个数,取结果的整数。比如得到结果是25.6,取整就是25。

  代码用/来表示:$get_int = floor(15/4)。15除以4,是一个小数3.75,向下取整就是3。一定是向下取整,向上取整就变成了4了。

 

 按照上面的规则:总共可以表示多少张表呢?32个库*每个库32个表=1024张表。如果想表的数量小,就把32改小一些。

 

 

上面是用计算机术语来表示, 下面用通俗的话描述。

 

1、库名称计算

 

用户id的后4位数,取32的模(取模就是除以这个数后,余多少)。余下的数,是0-31之间。

 

这样可以表示从0-31之间,总共32个数字。用这个32个数字代表着32个库名称:order_db_0、order_db_2.........................order_db_31

 

 

2、表名称计算

 

   最后要存储定到哪个表名里面去呢?

 

  用户id的最后4位数,除以32,取整数。将整数除以32,得到余数,能够表示从0-31之间32个数字,表示表名称。

 

  表名称类似这样:order_tb_1、order_tb_2..........................order_tb_31。一个库里面,总共32个表名称。

 

  比如用户id:19408064,用最后4位数字8064除以32,得到是251.9,取它的整数是251。

 

  接着将251除以32,取余数,余数为27。

 

  为了保持性能,每张表的数据量要控制。单表可以维持在一千万-5千万行的数据。1024*一千万。哇,可以表示很多数据了。

 

 

 

三、思考优点和缺点

 

优点

 

订单水平分库分表,为什么要按照用户id来切分呢?

 

好处:查询指定用户的所有订单,避免了跨库跨表查询。

 

 因为,根据一个用户的id来计算节点,用户的id是规定不变的,那么计算出的值永远是固定的(x库的x表)

  那么保存订单的时候,a用户的所有订单,都是在x库x表里面。需要查询a用户所有订单时,就不用进行跨库、跨表去查询了。

 

缺点

 

 这种方式也不是没有缺点。

 

  缺点在于:数据分散不均匀,某些表的数据量特别大,某些表的数据量很小。因为某些用户下单量多,打个比方,1000-2000这个范围内的用户,下单特别多,

  而他们的id根据计算规则,都是分到了x库x表。造成这个表的数据量大,单表的数据量撑到极限后,咋办呢?

 

  总结一下:每种分库分表方案也不是十全十美,都是有利有弊的。目前来说,这种使用用户id来切分订单数据的方案,还是被大部分公司给使用。实际效果还不错。程序员省事,至于数据量暴涨,以后再说呢。毕竟公司业务发展到什么程度,不知道的,项目存活期多久,未来不确定。先扛住再说。

 

比较好的方案是不是:又能均匀分散、又能避免单表数据量暴涨方便扩容。以前看过一篇文章介绍过使用节点来存储分库分表。笔者暂时没完整的思路。

 

 

二、查询需求的考虑

 

 

  

方案一的查询问题

 

   方案一的情况下,由于是按照订单号做分散数据到多个库、多个表。如果需要查询a用户的所有订单,咋办?需要跨库、跨表查询。

   这样效率低。不可行。

   

 

方案二的查询问题

 

如果是按照uid来切分订单数据,在实际应用中一些很频繁的查询需求像下面这样:

 

1、后台、前台,往往是输入一个订单号,查询这个订单的数据。select操作

2、然后修改这个订单的相关状态。update操作。

 

  由于是,按照用户编号将订单数据分散在各个库、各个表中。

 

  那输入订单号,怎么知道去哪个库、哪个表查询呢?不可能所有的库、所有表都查询一遍,效率太低,不可行。

 

 

 

 三、解决办法:建立用户id和订单号的索引关系表

 

      无论是根据用户id来切分订单,还是根据订单号切分数据。总不能十全十美的。

     

      写到这里,发现真的没有一种技术方案是十全十美的,看,使用用户id来切分订单,好处是有了,坏处也出来了。

 

      不过没关系,早要有心里承受:不要觉得技术是完美无缺的。针对这种情况,想办法去解决办法。

 

    思路:既然是根据订单号分散订单数据,如果需要知道某个用户所有的订单。只要我能知道了a用户的所有的订单号,那么就可以根据订单号定位到表名称了。

    思路:既然是根据用户id来分散订单数据的。那么只要知道了这个订单号是谁的(得到了用户id),就能知道去哪个库、哪个表查询数据了。

   

 

      那怎么知道是谁的呢?建立一个索引关系表,暂且叫做订单用户关系索引表order_user_idx。咱们命名为了保持维护性,还是一看能够知道是干嘛用的。

 

     存储的数据包括两项:订单号、用户编号。

 

     这样输入订单号,可以去查询索引关系表,获取到用户编号。

 

     得到了用户编号,问题解决了。订单信息是根据用户编号分库分表的,可以直接定位到x库x表了。

 

     当创建订单的时候,就要把关系插入到表里面去了。保存关系记录时,为了减低用户等待时间,不需要实时,做成异步。加入到消息队列中去操作。

    

 

 

     订单用户索引关系表的性能优化

 

     

     考虑到,一个用户的下的订单可能是几十个,也可能是几百个,随着时间的推移,会越来越多。这个索引关系表,也不能使用单表存储。

 

     所以对这个订单用户关系索引表,也要进行分库分表:直接根据订单号取模进行分库分表。是不是感觉挺麻烦了。确实麻烦。不过能解决问题就好。暂时没想到其他办法了。

 

    一个订单,在创建的时候,就已经分配好给指定用户了。只是一个关系对应,以后也不会变化。

 

    根据这个特点。订单用户索引关系表,其实可以放到内存中缓存起来应对查询需求(数据库那张索引关系表也要有,数据要持久化)。

 

    平时查询的时候,走内存缓存查询。如果查询不到,再走数据库查询一下关系。这样速度就很快了。

 

 

 

    结语:水平分表,其实折腾起来工作量挺大的,切分了后,出现新的问题,代码查询又得改,要提供其他解决办法。所以经常看到别人说,能不水平分表,尽量不要分,业务没达到瓶颈,先用硬件扛住,后面再考虑水平切分数据。看银行、联通这些有钱的企业,使用性能强劲的oracle搭配小型机服务器,单表的数据量达到十多亿。小型机是专门定制的,几十万一台。性能很强。分库分表是很耗费时间、当你交易量做到上亿规模的时候,那时,公司的实力应该可以了,经济方面有足够的实力聘请经验丰富的技术来重构。

 

 

 

 

 

 

思考一、b2b平台的订单分卖家和买家的时候,选择什么字段来分库分表呢?

 

上面讨论的情况是,b2c平台。订单的卖家就一个,就是平台自己。

 

b2b平台,上面支持开店,买家和卖家都要能够登陆看到自己的订单。

 

先来看看,分表使用买家id分库分表和根据卖家id分库分表,两种办法出现的问题

 

如果按买家id来分库分表。有卖家的商品,会有n个用户购买,他所有的订单,会分散到多个库多个表中去了,卖家查询自己的所有订单,跨库、跨表扫描,性能低下。

如果按卖家id分库分表。买家会在n个店铺下单。订单就会分散在多个库、多个表中。买家查询自己所有订单,同样要去所有的库、所有的表搜索,性能低下。

 

所以,无论是按照买家id切分订单表,还是按照卖家id切分订单表。两边都不讨好。

 

淘宝的做法是拆分买家库和卖家库,也就是两个库:买家库、卖家库。

买家库,按照用户的id来分库分表。卖家库,按照卖家的id来分库分表。

实际上是通过数据冗余解决的:一个订单,在买家库里面有,在卖家库里面也存储了一份。下订单的时候,要写两份数据。先把订单写入买家库里面去,然后通过消息中间件来同步订单数据到卖家库里面去。

 

买家库的订单a修改了后,要发异步消息,通知到卖家库去,更改状态。

 

 

思考二:那可以按订单号来分库分表吗?  

 

这样分库分表的话,用户有10个订单,订单不见得都在一个库、一个表里面。查询a用户的所有订单,就会变得麻烦了。尤其是要进行分页展示,分散在不同的表,甚至不同的数据库服务器,也比较耗费性能。

 

那么订单号里面,最好是要有分库分表信息。淘宝的是在订单号里面添加了卖家id末2位、买家id末2位。这样的好处是干嘛呢?直接定位到具体的库、具体的表去了?

 

怎么根据这个呢。因为分库、分表的规则,买家库是按照卖家id末尾2位数分,卖家库是按照卖家id末尾两位分。

 

所以,只要从订单号里面拿到了这些数字信息,就知道在哪个库,哪个表了。

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

 

按照这样,其实就没必要使用订单号来计算了?

如果是按照用户id的后4位数取模分散订单数据。那么订单号的生成,可以在后面加上用户id的后4位数。

那么,虽然是按照用户id来对订单表分库分表的。其实可以直接根据订单号,知道这个订单在哪个库哪个表了。

如果是b2b系统,涉及到卖家和买家。那么可以把卖家和买家的id后面4位都加进去。不过是不是订单号太长了?

 

 

思考三、按照订单的时间来分表如何?

 

一月一张表。一年一张表。用户的所有订单,会分散在不同的库、不同的表中。

 

按照时间分,在切分订单数据的时候,业界用得比较少。

 

出现如下两个问题:

 

1、如果需要分页查询某个用户的所有订单数据,就会出现跨库、跨表查询。效率低。

    可以做折中:限制只能查一个范围内的订单,比如一次只能查询,一年以内或者一个月以内的订单。

2、某个时间集中写入数据,出现瓶颈。如一个月一张表。这个月的订单量暴涨呢。那么写入新的订单数据都会操作这张表。造成性能低下。影响整个业务系统交易。

真正好的分表方案,尽量将写数据分散到多个表去,达到分流效果,系统的并发能力就提高了。

 

时间: 2024-11-05 22:06:40

订单表的分库分表方案设计(大数据)的相关文章

分库分表的几种常见玩法及如何解决跨库查询等问题

在谈论数据库架构和数据库优化的时候,我们经常会听到"分库分表"."分片"."Sharding"-这样的关键词.让人感到高兴的是,这些朋友所服务的公司业务量正在(或者即将面临)高速增长,技术方面也面临着一些挑战.让人感到担忧的是,他们系统真的就需要"分库分表"了吗?"分库分表"有那么容易实践吗?为此,笔者整理了分库分表中可能遇到的一些问题,并结合以往经验介绍了对应的解决思路和建议. 垂直分表 垂直分表在日常开

大众点评订单分库分表实践之路

本文是关于大众点评订单分库分表实践的一个具体分享,包含对订单库的具体切分策略,以及我个人的一些思考. 背景   订单单表早已突破两百G,因查询维度较多,即使加了两个从库,各种索引优化,依然存在很多查询不理想的情况.加之去年大量的抢购活动的开展,数据库达到瓶颈,应用只能通过限速.异步队列等对其进行保护.同时业务需求层出不穷,原有的订单模型很难满足业务需求,但是基于原订单表的DDL又非常吃力,无法达到业务要求.随着这些问题越来越突出,订单数据库的切分就愈发急迫了. 我们的目标是未来十年内不需要担心订

MySQL分库分表的实现过程详解介绍

MySQL分库分表基础表介绍 表基本模型结构 这里我们模拟一个商城的基本的表结.此结构由(用户.门店.导购.门店商品.订单.订单对应的商品).其中,导购也是一个用户,门店是只属于一个店主的,同时店主本身也是一个导购也是一个普通用户. 结构图:   构造数据脚本 MySQL分库分表(1)-脚本 对业务场景进行模拟 场景1:购买者下订单. 1.从session中获得客户ID. 2.可以通过时间戳等拼凑一个订单ID(在创建表的时候为了方便我用自增的,在以下我们一直就吧订单ID看成不是自增的,是用程序生

分库分表下uuid的生成

    分库分表时一般有必要自定义生成uuid,大企业一般有自己的uuid生成服务,其他它的实现很简单.我们以订单号为例,组成可以是"业务标识号+年月日+当日自增数字格式化",如0001201608140000020.当然,如果我们用"业务标识号+用户唯一标识+当前时间"也是可以达到uuid的目的的,但用户唯一标识是敏感信息且可能不太方便处理为数字,所以弄一套uuid生成服务是很有必要的.本文就来研究下怎么实现自增数字,且性能能满足企业中的多方业务调用.起初,我想的

水平分库分表的关键问题及解决思路

分片技术的由来 关系型数据库本身比较容易成为系统性能瓶颈,单机存储容量.连接数.处理能力等都很有限,数据库本身的"有状态性"导致了它并不像Web和应用服务器那么容易扩展.在互联网行业海量数据和高并发访问的考验下,聪明的技术人员提出了分库分表技术(有些地方也称为Sharding.分片).同时,流行的分布式系统中间件(例如MongoDB.ElasticSearch等)均自身友好支持Sharding,其原理和思想都是大同小异的. 分布式全局唯一ID 在很多中小项目中,我们往往直接使用数据库自

水平分库分表的关键步骤和技术难点

在之前的文章中,我介绍了分库分表的几种表现形式和玩法,也重点介绍了垂直分库所带来的问题和解决方法.本篇中,我们将继续聊聊水平分库分表的一些技巧. 分片技术的由来 关系型数据库本身比较容易成为系统性能瓶颈,单机存储容量.连接数.处理能力等都很有限,数据库本身的"有状态性"导致了它并不像Web和应用服务器那么容易扩展.在互联网行业海量数据和高并发访问的考验下,聪明的技术人员提出了分库分表技术(有些地方也称为Sharding.分片).同时,流行的分布式系统中间件(例如MongoDB.Elas

阿里云 DRDS 分库分表二维查询解决方案(RANGE_HASH拆分函数)

现有互联网业务模式下,数据库分库分表已经成为解决数据库瓶颈的一个普遍的解决方案.分库分表有多种好处,比如高容量.大并发等,但是在拆分过程中也引入了一些使用限制,比如多维查询,非拆分键的查询请求会分发到底层所有实例进行查询,性能会大打折扣. 我们来举个例子,最常见的订单表,常用的拆分方法是按照用户 ID 作为拆分键.如果仅使用订单号作为条件来查询则会出现上述性能问题,而仅通过订单号的查询请求恰恰占有不小的比例. 在阿里云提供的新版 DRDS(5.1.28-1320920 及其以上的版本)已经实现二

详解yii2实现分库分表的方案与思路

前言 大家可以从任何一个gii生成model类开始代码上溯,会发现:yii2的model层基于ActiveRecord实现DAO访问数据库的能力. 而ActiveRecord的继承链可以继续上溯,最终会发现model其实是一个component,而component是yii2做IOC的重要组成部分,提供了behaviors,event的能力供继承者扩展. (IOC,component,behaviors,event等概念可以参考http://www.digpage.com/学习) 先不考虑上面的

透明的分库分表方案

问题提出 随着应用规模的不断扩大,单机数据库就慢慢无法满足应用的需要了,这主要表现在如下方面: 存量数据越来越大,查询速度越来越慢 访问并发越来越大,磁盘IO.网络IO.CPU都慢慢成为瓶颈 事务数越来越多,事务冲突越来越严重,导致TPS越来越少 这个时候,有的人采用了换商用数据库的方案比如Oracle,然后用Oracle的RAC方式进行水平扩展.但是带来的缺点也比较明显,第一是成本太高,一般人吃不消:第二,管理复杂度较单节点有非常大的提升,风险及管理成本也相应增加:第三,对人员的水平要求更高,