分库分表解释以及问题出现

前言

博主github

博主个人博客http://blog.healerjean.com

1、解释

我们知道互联网是由非常庞大的用户组成,所以肯定有非常绝大的请求,这些请求又会产生非常巨大的信息存储在数据库中,由于数据量非常巨大,单个数据库的表示很难容纳所有数据,所以就有了分库分表的需求。 对于数据的拆分主要有两个方面 :垂直拆分和水平拆分

1.1、垂直拆分

垂直拆分: 根据业务的维度,将原本的一个库(表)拆分为多个库(表〉,每个库(表)
与原有的结构不同。

1.1.1、垂直分表

也就是“大表拆小表”,基于列字段进行的。一般是表中的字段较多,将不常用的, 数据较大,长度较长(比如text类型字段)的拆分到“扩展表“。 一般是针对那种几百列的大表,也避免查询时,数据量太大造成的“跨页”问题。

1.1.2、垂直分库

垂直分库针对的是一个系统中的不同业务进行拆分,按照业务把不同的数据放到不同的库中。其实在一个大型而且臃肿的数据库中表和表之间的数据很多是没有关系的,比如用户User一个库,商品Producet一个库,订单Order一个库。 切分后,要放在多个服务器上,而不是一个服务器上

1.2、水平拆分

水平拆分: 根据分片(sharding )算法,将一个库(表)拆分为多个库(表),每个库(表)依旧保留原有的结构。

1.2.1、水平分表

1568187931226

针对数据量巨大的单张表(比如订单表),按照某种规则(Hash取模地理区域时间等),切分到多张表里面去。 但是这些表还是在同一个库中

**结果:分表能解决数据量过大造成的查询效率低下的问题 **

问题:但是无法有效解决数据的并发访问能力。,所以库级别的数据库操作还是有IO瓶颈。不建议采用。

1.2.2、水平分库+分表

1568187963745

将数据库拆分,提高数据库的写入能力就是所谓的分库。将单张表的数据切分到多个数据库中,表的结构是一样的 。

结果: 水平分库分表能够有效的缓解单机和单库的性能瓶颈和压力,突破IO、连接数、硬件资源等的瓶颈。

1.3、水平分库分表的规则

路由:通过分库分表规则查找到对应的表和库的过程叫作路由。例如,分库分表的规则是user_id % 4,当用户新注册了一个账号时,假设用户的ID是123,我们就可以通过123 % 4 = 3确定此账号应该被保存在User3表中。当ID为123的用户登录时,我们可通过123 % 4 = 3计算后,确定其被记录在User3中。

1.3.1 、Hash取模

对hash结果取余数 (hash() mod N):对机器编号从0到N-1,按照自定义的hash()算法,对每个请求的hash()值按N取模,得到余数i,然后将请求分发到编号为i的机器

使用场景哈希分片常常应用于数据没有时效性的情况

有一家公司在一年内能做10亿条交易,假设每个数据库分片能够容纳5000万条数据,则至少需要20个表才能容纳10亿条交易。在路由时,我们根据交易ID进行哈希取模来找到数据属于哪个分片,因此,在设计系统时要充分考虑如何设计数据库的分库分表的路由规则。

1.3.2 、地理区域

比如按照华东,华南,华北这样来区分业务

使用场景:比如我们购买ECS服务器数据,以及阿里云图片服务器等。

1.3.3 、时间

按照时间切分,就是将6个月前,甚至一年前的数据切出去放到另外的一张表,因为随着时间流逝,这些表的数据 被查询的概率变小,所以没必要和“热数据”放在一起,这个也是“冷热数据分离”。

使用场景切片方式适用于有明显时间特点的数据

比如一个用户的订单交易数据,我们可以根据月或者季度进行切片,具体由交易数据量来决定以什么样的时间周期进行切割

2、分库分表后的问题

2.1、事务

分库分表后,就成了分布式事务了。如果依赖数据库本身的分布式事务管理功能去执行事务,将付出高昂的性能代价; 如果由应用程序去协助控制,形成程序逻辑上的事务,又会造成编程方面的负担。

2.2、SQl问题

2.2.1、多库结果集合并(group by,order by),跨库join

分库分表后表之间的关联操作将受到限制,我们无法join位于不同分库的表,也无法join分表粒度不同的表, 结果原本一次查询能够完成的业务,可能需要多次查询才能完成。

2.2.2、分页问题

分库后,有些分页查询需要遍历所有库。 举个分页的例子,比如要求按时间顺序展示某个商家的订单,每页100条记录,假设库数量是8,我们来看下分页处理逻辑:

1、全局视野法:
  • 如果取第1页数据,则需要从每个库里按时间顺序取前100条记录,8个库汇总后有800条,然后对这800条记录在应用里进行二次排序,最后取前100条。

  • 如果取第10页数据,则需要从每个库里取前1000(100*10)条记录,汇总后有8000条记录,然后对这8000条记录二次排序后取(900,1000)条记录。

  • 分库情况下,对于第k页记录,每个库要多取100*(k-1)条记录,所有库加起来,多取的记录更多,所以越是靠后的分页,系统要耗费更多内存和执行时间。

优点:对比没分库的情况,无论取那一页,都只要从单个DB里取100条记录,而且无需在应用内部做二次排序,非常简单。

缺点每个分库都需要返回更多的数据,增大网络传输量除了数据库要按照time排序,服务层也需要二次排序,损耗性能;随着页码的增大,性能极具下降,数据量和排序量都将大增

2、业务折中

禁止跳页查询不提供“直接跳到指定页面”的功能,只提供下一页的功能正常来讲,不管哪一个分库的第3页都不一定有全局第3页的所有数据,例如一下三种情况:

  1. 先找到上一页的time的最大值(可从前台传入),作为第二页数据拉去的查询条件,只取每页的记录数
  2. 这样服务层还是获得两页数据,再做一次排序,获取一页数据。
  3. 改进了不会因为页码增大而导致数据的传输量和排序量增大
3、允许数据精度丢失

需要考虑业务员上是否接受在页码较大是返回的数据不是精准的数据。

  • 在数据量较大,且ID映射分布足够随机的话,应该是满足等概率分布的情况的,所以取一页的数据,我们在每个数据库中取(每页数据/数据库数量)个数据。
  • 当然这样的到的结果并不是精准的,但是当实际业务可以接受的话, 此时的技术方案的复杂度变大大降低。也不需要服务层内存排序了。
4、二次查询法

2 个数据库,假设一页只有5条数据,查询第200页的SQL语句为

select * from T order by time  limit 1000  5;
  1. 讲sql改写为
select * from T order by time limit  500 5;  

注意这里的500=1000/分表数量,并将这个sql下发至每个分库分表中执行,每个分库返回这个sql执行的结果。
  1. 找到所有分库返回结果的time的最小值

1568872807187

第一个库,5条数据的time最小值是1487501123
第二个库,5条数据的time最小值是1487501223

故,三页数据中,time最小值来自第一个库,time_min=1487501123,这个过程只需要比较各个分库第一条数据,时间复杂度很低
  1. 查询二次改写,第二次要改写成一个between语句,between的起点是time_min,between的终点是原来每个分库各自返回数据的最大值:
第一个分库,第一次返回数据的最大值是1487501523
所以查询改写为select * from T order by time where time between time_min and 1487501523


第二个分库,第一次返回数据的最大值是1487501699
所以查询改写为select * from T order by time where time between time_min and 1487501699

1568872940023

从上面图片可以看出,DB1比第一次查出来的数据多了两行,应为查询的范围扩大了
  1. 计算time_min这条记录在全局的偏移量

1568873945123

从而我们得知time_min这条记录在全局的偏移量值=500+497=997,其实也就是说,我们的第1000条记录的终点是time=1487501128

  1. 获取最终结果,讲第二次查询出的进行排序,最终获得结果

1568874155994

**优点:可以精确的返回业务所需数据,每次返回的数据量都非常小,不会随着翻页增加数据的返回量。 **

缺点:需要进行两次数据库查询

2.2.3、Join问题

互联网公司的业务,往往是并发场景多,DB查询频繁,有一定用户规模后,往往要做分库分表。 分库分表Join肯定是不行的

2.2.3.1、不使用join的原因:
  1. join的话,是走嵌套查询的。小表驱动大表,且通过索引字段进行关联。如果表记录比较少的话,还是OK的当表处于百万级别后,join导致性能下降;
  2. 分布式的分库分表。这种时候是不建议跨库join的。目前mysql的分布式中间件,跨库join表现不良。
  3. join写的sql语句要修改,不容易发现,成本比较大,当系统比较大时,不好维护。
  4. 数据库是最底层的,一个系统性能好坏的瓶颈往往是数据库。建议数据库只是作为数据存储的工具,而不要添加业务上去。

2.2.3.2、不使用join的解决方法:

应用层面解决 :可以更容易对数据进行分库,更容易做到高性能和可扩展。(记得在小米金融供应链关联查询卖方,卖方 核心企业,授信企业的使用,就是这样,本来其实是两个企业,但是却有4个子段表示join查询肯定是不好的)

  • 缓存的效率更高。许多应用程序可以方便地缓存单表查询对应的结果对象。,如果某个表很少改变,那么基于该表的查询就可以重复利用查询缓存结果了。单表查询出数据后,作为条件给下一个单表查询

  • 查询本身效率也可能会有所提升。查询id集的时候,使用IN()代替关联查询,可以让MySQL按照ID顺序进行查询,这可能比随机的关联要更高效。mysql对in的数量没有限制,mysql限制整条sql语句的大小。通过调整参数max_allowed_packet ,可以修改一条sql的最大值。建议在业务上做好处理,限制一次查询出来的结果集是能接受的,但是最好不要超过500条(小米规范)

  • 可以减少多次重复查询。在应用层做关联查询,意味着对于某条记录应用只需要查询一次,而在数据库中做关联查询,则可能需要重复地访问一部分数据。从这点看,这样的重构还可能会减少网络和内存的消艳。

2.3、数据迁移、容量规划

3、借鉴的博主

ContactAuthor

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章