原创文章,转载注明出处程序员
1、两种方案分库分表数据库
通常业界,对订单数据的分库分表,笔者了解,有两类思路:按照订单号来切分、按照用户id来切分。缓存
方案1、按照订单号来作hash分散订单数据性能优化
把订单号看做是一个字符串,作hash,分散到多个服务器去。服务器
具体到哪一个库、哪一个表存储数据呢?订单号里面的数字来记录着。微信
如今的微信红包。它的订单分库分表,是对订单号进行hash计算。不是什么取模、取整数。这样数据是均匀分散的。并发
而后订单号的末尾3个数,里面是包含了库名称、表名称的。oracle
若是要查询某用户的全部订单呢?异步
因为是根据订单号来分散数据的。他的订单分散在了多个库、多个表中。性能
总不能去全部的库,全部的表扫描吧。这样效率很低。
其实按照uid切分订单,同样会遇到查询的问题。好比要查询a订单的信息。分库分表的规则是按照uid,都不知道数据在哪一个库,无从查。
后续说明解决思路。
通常使用方案二的比较多,一个用户的全部订单,都在一张表里面,那么作分页展现的时候,就容易。
方案2、按照用户id打散订单数据。
以uid来切分数据,有两种思路:
一种是,某个范围的uid订单到哪些库。0到2千万uid,对应的订单数据到a库、a表。2千万到4千万对应的订单到b库。
为何这种方案用得比较少呢?
容易出现瓶颈吗。某个范围内的用户,下单量比较多,那么形成这个库的压力特别大。其余库却没什么压力。
第二种是,使用uid取模运算。第二种方案业界用得比较多。
一方面、处理简单,程序上作取模运算就行了。
另外一方面、使用取模的方式,数据比较均匀分散到多个库去了。不容易出现单个库性能瓶颈。
可是很差处也有:即要扩容的时候,比较麻烦。就须要迁移数据了。
要扩容的时候,为了减小迁移的数据量,通常扩容是以倍数的形式增长。好比原来是8个库,扩容的时候,就要增长到16个库,再次扩容,就增长到32个库。这样迁移的数据量,就小不少了。这个问题不算很大问题,毕竟一次扩容,能够保证比较长的时间,并且使用倍数增长的方式,已经减小了数据迁移量。
下面笔者,分析一下按照用户id取模的方式分库分表。
按照用户id做为key来切分订单数据,具体以下:
一、 库名称定位:用户id末尾4位 Mod 32。
Mod表示除以一个数后,取余下的数。好比除以32后,余下8,余数就是8。
代码符号是用%表示:15%4=3。
二、表名称定位:(用户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改小一些。
上面是用计算机术语来表示, 下面用通俗的话描述。
一、库名称计算
用户id的后4位数,取32的模(取模就是除以这个数后,余多少)。余下的数,是0-31之间。
这样能够表示从0-31之间,总共32个数字。用这个32个数字表明着32个库名称:order_db_0、order_db_2.........................order_db_31
二、表名称计算
最后要存储定到哪一个表名里面去呢?
用户id的最后4位数,除以32,取整数。将整数除以32,获得余数,可以表示从0-31之间32个数字,表示表名称。
表名称相似这样:order_tb_一、order_tb_2..........................order_tb_31。一个库里面,总共32个表名称。
好比用户id:19408064,用最后4位数字8064除以32,获得是251.9,取它的整数是251。
接着将251除以32,取余数,余数为27。
为了保持性能,每张表的数据量要控制。单表能够维持在一千万-5千万行的数据。1024*一千万。哇,能够表示不少数据了。
3、思考优势和缺点
优势
订单水平分库分表,为何要按照用户id来切分呢?
好处:查询指定用户的全部订单,避免了跨库跨表查询。
由于,根据一个用户的id来计算节点,用户的id是规定不变的,那么计算出的值永远是固定的(x库的x表)
那么保存订单的时候,a用户的全部订单,都是在x库x表里面。须要查询a用户全部订单时,就不用进行跨库、跨表去查询了。
缺点
这种方式也不是没有缺点。
缺点在于:数据分散不均匀,某些表的数据量特别大,某些表的数据量很小。由于某些用户下单量多,打个比方,1000-2000这个范围内的用户,下单特别多,
而他们的id根据计算规则,都是分到了x库x表。形成这个表的数据量大,单表的数据量撑到极限后,咋办呢?
总结一下:每种分库分表方案也不是十全十美,都是有利有弊的。目前来讲,这种使用用户id来切分订单数据的方案,仍是被大部分公司给使用。实际效果还不错。程序员省事,至于数据量暴涨,之后再说呢。毕竟公司业务发展到什么程度,不知道的,项目存活期多久,将来不肯定。先扛住再说。
比较好的方案是否是:又能均匀分散、又能避免单表数据量暴涨方便扩容。之前看过一篇文章介绍过使用节点来存储分库分表。笔者暂时没完整的思路。
2、查询需求的考虑
方案一的查询问题
方案一的状况下,因为是按照订单号作分散数据到多个库、多个表。若是须要查询a用户的全部订单,咋办?须要跨库、跨表查询。
这样效率低。不可行。
方案二的查询问题
若是是按照uid来切分订单数据,在实际应用中一些很频繁的查询需求像下面这样:
一、后台、前台,每每是输入一个订单号,查询这个订单的数据。select操做
二、而后修改这个订单的相关状态。update操做。
因为是,按照用户编号将订单数据分散在各个库、各个表中。
那输入订单号,怎么知道去哪一个库、哪一个表查询呢?不可能全部的库、全部表都查询一遍,效率过低,不可行。
3、解决办法:创建用户id和订单号的索引关系表
不管是根据用户id来切分订单,仍是根据订单号切分数据。总不能十全十美的。
写到这里,发现真的没有一种技术方案是十全十美的,看,使用用户id来切分订单,好处是有了,坏处也出来了。
不过不要紧,早要有内心承受:不要以为技术是天衣无缝的。针对这种状况,想办法去解决办法。
思路:既然是根据订单号分散订单数据,若是须要知道某个用户全部的订单。只要我能知道了a用户的全部的订单号,那么就能够根据订单号定位到表名称了。
思路:既然是根据用户id来分散订单数据的。那么只要知道了这个订单号是谁的(获得了用户id),就能知道去哪一个库、哪一个表查询数据了。
那怎么知道是谁的呢?创建一个索引关系表,暂且叫作订单用户关系索引表order_user_idx。我们命名为了保持维护性,仍是一看可以知道是干吗用的。
存储的数据包括两项:订单号、用户编号。
这样输入订单号,能够去查询索引关系表,获取到用户编号。
获得了用户编号,问题解决了。订单信息是根据用户编号分库分表的,能够直接定位到x库x表了。
当建立订单的时候,就要把关系插入到表里面去了。保存关系记录时,为了减低用户等待时间,不须要实时,作成异步。加入到消息队列中去操做。
订单用户索引关系表的性能优化
考虑到,一个用户的下的订单多是几十个,也多是几百个,随着时间的推移,会愈来愈多。这个索引关系表,也不能使用单表存储。
因此对这个订单用户关系索引表,也要进行分库分表:直接根据订单号取模进行分库分表。是否是感受挺麻烦了。确实麻烦。不过能解决问题就好。暂时没想到其余办法了。
一个订单,在建立的时候,就已经分配好给指定用户了。只是一个关系对应,之后也不会变化。
根据这个特色。订单用户索引关系表,其实能够放到内存中缓存起来应对查询需求(数据库那张索引关系表也要有,数据要持久化)。
平时查询的时候,走内存缓存查询。若是查询不到,再走数据库查询一下关系。这样速度就很快了。
结语:水平分表,其实折腾起来工做量挺大的,切分了后,出现新的问题,代码查询又得改,要提供其余解决办法。因此常常看到别人说,能不水平分表,尽可能不要分,业务没达到瓶颈,先用硬件扛住,后面再考虑水平切分数据。看银行、联通这些有钱的企业,使用性能强劲的oracle搭配小型机服务器,单表的数据量达到十多亿。小型机是专门定制的,几十万一台。性能很强。分库分表是很耗费时间、当你交易量作到上亿规模的时候,那时,公司的实力应该能够了,经济方面有足够的实力聘请经验丰富的技术来重构。
思考1、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位都加进去。不过是否是订单号太长了?
思考3、按照订单的时间来分表如何?
一月一张表。一年一张表。用户的全部订单,会分散在不一样的库、不一样的表中。
按照时间分,在切分订单数据的时候,业界用得比较少。
出现以下两个问题:
一、若是须要分页查询某个用户的全部订单数据,就会出现跨库、跨表查询。效率低。
能够作折中:限制只能查一个范围内的订单,好比一次只能查询,一年之内或者一个月之内的订单。
二、某个时间集中写入数据,出现瓶颈。如一个月一张表。这个月的订单量暴涨呢。那么写入新的订单数据都会操做这张表。形成性能低下。影响整个业务系统交易。
真正好的分表方案,尽可能将写数据分散到多个表去,达到分流效果,系统的并发能力就提升了。