下载安卓APP箭头
箭头给我发消息

客服QQ:3315713922

MySQL 水平拆分

作者:课课家教育     来源: http://www.kokojia.com点击数:665发布时间: 2017-08-20 08:00:55

标签: 数据库MySQL数据库基础

  随着数据库表中数据日积月累越来越多,当表记录数达到千万甚至亿级别时,数据库表的访问效率下降明显,导致外层应用的访问效率非常差,访问时间急剧上升,用户体验下降。如果是表数据太大的原因导致访问速度变慢,一般情况下当访问与此表相关业务时速度会很慢,而访问与此表无关的业务时速度会很快。

  从这个问题来看,我们可以用从问题现象,原因分析,解决方案这样的思路来分析思考问题~

  分析上面的问题现象,明显的一个原因是因为某些表的数据记录太多的原因,导致数据库访问效率下降造成的。

  既然是某些表数据记录太多的原因,那我们的解决办法当然是让这些表的数据记录减少到不影响访问效率为止,同时为了考虑以后这些数据还是会不断的增长,为了让这些数据增长后还是可以扩展,那需要考虑如何可以将这些数据无限制的水平拆分,而不需要修改上层应用,一般来说只要设计得当,从理论上讲水平拆分都是可以无限扩展的。

  那我们先把记录数太多的表分成多张表,这时问题来了。

  1、对记录数多的表我们进行拆分,那对与之相关联的一些表该怎么办?这个问题其实也是现实开发中比较普遍的一个问题,现在数据库表一般都会与其他表有关联的。有人会提出一个方法就是所有的表都不与其他表关联,至少在SQL执行层面上如此,这样不就解决了数据或业务关联问题了啊,但这里有个问题那就是如果都按照这样在SQL层面完全解耦,而在应用层面再关联的话,会导致数据库访问次数增加很多,而且网络传输数据增加,比如A表和B表是关联表,如果在SQL层面关联,则只执行一个SQL;如果在SQL层面独立,则需要执行两个SQL,分别查询出A表数据和B表数据,因为没有条件关联过滤,则数据肯定比执行关联SQL多很多,然后再在应用层进行关联。所以我个人觉得对性能要求高的系统中,还是需要使用SQL层面的关联的,但这里有一个原则肯定是要遵守,那就是不能让多个需要拆分表关联,因为这会导致拆分标准不一致而导致无法拆分。对关联SQL中的一个表需要拆分,其他都是相对静态的无需拆分的表,这种情况下的解决思路是将需拆分表拆分到多个库中,而静态表则同步到各个拆分库中。这里再上升一下,分析一下系统的表结构中,一般会分动态表(数据变化很大,数据量也可能很大的表)和静态表(数据变化很小的表,一般来说都是基础表,数据量也不会很大),将基础的静态表都放到一个公共库中,将动态表根据标准分拆到分库中,拆分完成后基础数据都在公共库维护,并同步到分库中,在分库中维护动态表,同时在查询时动态表可以与分库中静态表关联查询,这样就解决了这个问题。

  2、数据库表拆分的标准又是什么,按照什么来拆分?一般来说这个拆分标准可以按照数据范围分,比如1-100万一个表,100万-200万又是一个表;也可以按照时间顺序来拆分,比如一年的数据归到一张表中等;也可以按照地域范围来分,比如按照地市来分,每个或多个地市一个库等,反正这个个人觉得是按照具体的情况来分的,一般情况下,对带有较浓的分割标志的数据库表,可以根据分割标志来分割,对没有较浓分割标志的数据库表,则只能按照最笨的方法如数据范围来拆分了,有时候为了增加拆分质量,还可以先根据一个分割标志来分表,在根据另一个分割标志来分区等复合式的拆分方式来水平拆分数据库表。

  3、数据库表水平拆分后,访问数据库表的SQL必须要带上分割标志来确定目标数据库表,如果要对多个拆分数据库表进行查询,则需要通过多次访问数据库表来完成,同时在应用层面将数据合并来做到。但有时候,一般需要可以通过至少两种方式(或分割标志)来获取目标数据库表。举个例子,大型网络游戏中因为玩家太多了(比如达到几千万甚至亿级别时),所以将玩家的用户信息分库分表,当用户登录时,现在一般的做法都是会让用户自己选择是哪一区的,根据这个选择来确定目标数据库表,但如果我们改一下,用户不知道自己是哪一区的,只知道自己的用户编号,输入用户编号后需要由系统自动根据用户编号来路由到目标数据库表。在这种情况下,个人觉得需要有一个规则来保证用户编号的规律性,比如可以在用户申请时,针对选择的不同区来生成不同的用户编号,比如1区是aaa+8位的顺序编号,2区是bbb+8位的顺序编号,这样的话,对aaa,bbb之类的分类编号是可以通过数据库表来管理的,比如用户编号是aaa开头和abc开头的都是1区的用户这样的规则就可以管理起来,这样当用户输入用户编号时,系统通过截取用户编号前三位,并到数据库表中查询出这前三位对应的哪个区,这样就可以获得这个用户的目标数据库表了。等到查询出这个用户信息后,这个用户信息中必定会存在分割标志信息的(这个例子中就是属于哪个区的),对这个用户信息缓存,就不再需要使用之前那种方式来确定目标数据库表了,而只需要根据缓存的用户信息中的属于哪个区的信息就可以来确定目标数据库表了。分析这两种确定目标数据库表的方式,一般来说前一种方式比较复杂,性能上消耗也较多,这种方式只有在第二种方式无法判断的情况下使用,所以使用频率相对来说非常低,而第二种方式则相对简单,而且性能也很好,这种方式是默认使用方式,使用频率相对来说非常高,但有时候因为信息不全无法使用第二种方式,所以必须要有前一种方式来补充使用。

  4、数据库水平拆分成多个库时,这时有一个事情是必须会碰到的,那就是数据库的连接。一般来说,应用服务器或应用系统对数据库连接的管理一般会通过连接池来管理,这样可以大大提高效率,而不会使用动态连接。这里就出来一个问题,当一个数据库水平拆分成多个数据库时,必然数据库连接池也会增加到多个,在一定范围内应该是不会有问题的,但毕竟应用服务器的性能也是有上限的,当数据库水平拆分成N个数据库时,应用服务器的性能就会吃不消了,这时候需要对应用服务器进行扩展了,比如一台应用服务器对应几个数据库连接等,当然这是比较深入的事情了,这里只把问题抛出来,不再多说。

  5、说到这里要说一下应用设计开发上的问题,首先是对目标数据库表路由模块必须要独立,如果路由不独立出来,那以后万一路由策略变更的话会死的很惨,而且路由算法是一个典型的策略模式应用,最好能实现成策略模式,以方便以后路由策略变更时应用可以无缝的切换路由策略。其次是对开发来说,最好能使用ibatis之类的持久层,既有一定的封装,也可以将SQL独立配置,这样在开发人员开发时,可以灵活的写SQL来实现逻辑,也可以对SQL语句进行管理,同时DBA可以很方便的对这些SQL进行专业的优化,而与应用开发无关。现在一些先行者已经在努力实现将数据库拆分的影响封装在代理中的项目,比如变形虫项目,这些项目的出现将会使数据库拆分后对应用开发的影响越来越小。最后一个是事务,如果可以接受分布式事务的性能那当然是最好的;如果不能接受,那一般的做法就是事务补偿(指在同一业务操作中当事务A提交,但事务B发生错误回滚后,为保持操作一致性和数据正确性,必须要做事务A操作的反操作来补偿事务A的提交,消除事务A的提交对数据结果的影响),但事务补偿会增加开发的工作量等问题;或者不是非常重要的业务操作时,通过保证事务B执行的成功率(比如先进行查询或预执行操作),从而使事务B的失败率下降到可以忽略的程度,从而可以不考虑事务的问题。

  6、还有一个非常重要的需要说一下,一般来说很多都是对原有系统的改造,这样的话就必然会有需要对原有数据的处理割接,这块工作也是非常重要的,数据库拆分方案做得最好,如果原有数据不能无缝的割接到新的拆分后的数据库中的话,那都是白搭。另外还有业务层面的问题,比如数据库表拆分引起的业务流程更改,业务操作习惯更改等方面的问题也要提早考虑和解决。

  总之,数据库表水平拆分是非常复杂的,需要综合各个方面考虑完善,套用网友cauherk的说法“系统的切分是个很复杂的技术活,要综合考虑,而不仅仅从数据库层面考虑。业务的使用、分库的原则、数据的割接、开发的侵入、可操作的易难程度、后期的管理等等都是需要考虑的因素。”。

  了解了以上六个问题,下面继续来看一下数据库的水平拆分的具体内容!

  一、水平拆分的介绍

  一般来说,简单的水平切分主要是将某个访问极其平凡的表再按照某个字段的某种规则来分散到多个表之中,每个表中包含一部分数据。

  简单来说,我们可以将数据的水平切分理解为是按照数据行的切分,就是将表中的某些行切分到一个数据库,而另外的某些行又切分到其他的数据库中。当然,为了能够比较容易的判定各行数据被切分到哪个数据库中了,切分总是都需要按照某种特定的规则来进行的。

  如根据某个数字类型字段基于特定数目取模,某个时间类型字段的范围,或者是某个字符类型字段的hash值。如果整个系统中大部分核心表都可以通过某个字段来进行关联,那这个字段自然是一个进行水平分区的上上之选了,当然,非常特殊无法使用就只能另选其他了。

  二、水平拆分的优缺点

  1.水平拆分的优点:

  ◆表关联基本能够在数据库端全部完成;

  ◆不会存在某些超大型数据量和高负载的表遇到瓶颈的问题;

  ◆应用程序端整体架构改动相对较少;

  ◆事务处理相对简单;

  ◆只要切分规则能够定义好,基本上较难遇到扩展性限制;

  2.水平切分的缺点:

  ◆切分规则相对更为复杂,很难抽象出一个能够满足整个数据库的切分规则;

  ◆后期数据的维护难度有所增加,人为手工定位数据更困难;

  ◆应用系统各模块耦合度较高,可能会对后面数据的迁移拆分造成一定的困难。

  三、拆分的规则

  1.根据取模水平拆分

  一般来说,像现在互联网非常火爆的互联网公司,特别是电商和游戏业务,基本上大部分数据都能够通过会员用户信息关联上,可能很多核心表都非常适合通过会员ID来进行数据的水平切分。而像论坛社区讨论系统,就更容易切分了,非常容易按照论坛编号来进行数据的水平切分。

  切分之后基本上不会出现各个库之间的交互。

  所以,对于我们的示例数据库来说,大部分的表都可以根据用户ID来进行水平的切分。

  不同用户相关的数据进行切分之后存放在不同的数据库中。如将所有用户ID通过5取模然后分别存放于两个不同的数据库中。每个和用户ID关联上的表都可以这样切分。这样,基本上每个用户相关的数据,都在同一个数据库中,即使是需要关联,也可以非常简单的关联上。

  2.根据区域来水平切分

  比如全国划分为10大片区,江浙沪算一哥,齐鲁算一个,两广算一个,两湖算一个,中原算一个,西南算一个,内蒙一个,东北一个,西北一个,华北一个,东南一个。

  在业务量比较大的华北、东南、江浙沪、两广片区的服务器可以分配较多的服务器资源,比如cpu、io、网络等等可以用比较好的高端配置。

  在业务量正常的西北、齐鲁、两湖、东北的服务器可以分配中高端的服务器资源。

  在业务量比较少的,西南、内蒙、中原的服务器可以稍微一般服务器即可。

  当然这些资源划分不能对外明示,我们在做内部规划的时候考虑好就可以了,免得被人诟病说有所偏颇不重视之类的。

  PS:这种划分不是定性的,根据业务可以随时将业务好的片区的资源升级。

  如下图所示:

 

  四、水平拆分与应用的整合视图

四、水平拆分与应用的整合视图

  五、水平拆分后续的问题

  在实施数据切分方案之前,有些可能存在的问题我们还是需要做一些分析的。一般来说,

  我们可能遇到的问题主要会有以下几点:

  ◆引入分布式事务的问题;

  ◆跨节点Join的问题;

  ◆跨节点合并排序分页问题;

  1.引入分布式事务的问题

  一旦数据进行切分被分别存放在多个MySQLServer中之后,不管我们的切分规则设计的多么的完美(实际上并不存在完美的切分规则),都可能造成之前的某些事务所涉及到的数据已经不在同一个MySQLServer中了。

  在这样的场景下,如果我们的应用程序仍然按照老的解决方案,那么势必需要引入分布式事务来解决。而在MySQL各个版本中,只有从MySQL5.0开始以后的各个版本才开始对分布式事务提供支持,而且目前仅有Innodb提供分布式事务支持。不仅如此,即使我们刚好使用了支持分布式事务的MySQL版本,同时也是使用的Innodb存储引擎,分布式事务本身对于系统资源的消耗就是很大的,性能本身也并不是太高。而且引入分布式事务本身在异常处理方面就会带来较多比较难控制的因素。

  怎么办?其实我们可以可以通过一个变通的方法来解决这种问题,首先需要考虑的一件事情就是:是否数据库是唯一一个能够解决事务的地方呢?其实并不是这样的,我们完全可以结合数据库以及应用程序两者来共同解决。各个数据库解决自己身上的事务,然后通过应用程序来控制多个数据库上面的事务。

  也就是说,只要我们愿意,完全可以将一个跨多个数据库的分布式事务分拆成多个仅处于单个数据库上面的小事务,并通过应用程序来总控各个小事务。当然,这样作的要求就是我们的俄应用程序必须要有足够的健壮性,当然也会给应用程序带来一些技术难度。

  2.跨节点Join的问题

  上面介绍了可能引入分布式事务的问题,现在我们再看看需要跨节点Join的问题。数据切分之后,可能会造成有些老的Join语句无法继续使用,因为Join使用的数据源可能被切分到多个MySQLServer中了。

  怎么办?这个问题从MySQL数据库角度来看,如果非得在数据库端来直接解决的话,恐怕只能通过MySQL一种特殊的存储引擎Federated来解决了。Federated存储引擎是MySQL解决类似于Oracle的DBLink之类问题的解决方案。和OracleDBLink的主要区别在于Federated会保存一份远端表结构的定义信息在本地。咋一看,Federated确实是解决跨节点Join非常好的解决方案。但是我们还应该清楚一点,那就似乎如果远端的表结构发生了变更,本地的表定义信息是不会跟着发生相应变化的。如果在更新远端表结构的时候并没有更新本地的Federated表定义信息,就很可能造成Query运行出错,无法得到正确的结果。

  对待这类问题,我还是推荐通过应用程序来进行处理,先在驱动表所在的MySQLServer中取出相应的驱动结果集,然后根据驱动结果集再到被驱动表所在的MySQLServer中取出相应的数据。可能很多读者朋友会认为这样做对性能会产生一定的影响,是的,确实是会对性能有一定的负面影响,但是除了此法,基本上没有太多其他更好的解决办法了。而且,由于数据库通过较好的扩展之后,每台MySQLServer的负载就可以得到较好的控制,单纯针对单条Query来说,其响应时间可能比不切分之前要提高一些,所以性能方面所带来的负面影响也并不是太大。更何况,类似于这种需要跨节点Join的需求也并不是太多,相对于总体性能而言,可能也只是很小一部分而已。所以为了整体性能的考虑,偶尔牺牲那么一点点,其实是值得的,毕竟系统优化本身就是存在很多取舍和平衡的过程。

  3.跨节点合并排序分页问题

  一旦进行了数据的水平切分之后,可能就并不仅仅只有跨节点Join无法正常运行,有些排序分页的Query语句的数据源可能也会被切分到多个节点,这样造成的直接后果就是这些排序分页Query无法继续正常运行。其实这和跨节点Join是一个道理,数据源存在于多个节点上,要通过一个Query来解决,就和跨节点Join是一样的操作。同样Federated也可以部分解决,当然存在的风险也一样。

  还是同样的问题,怎么办?我同样仍然继续建议通过应用程序来解决。

  如何解决?解决的思路大体上和跨节点Join的解决类似,但是有一点和跨节点Join不太一样,Join很多时候都有一个驱动与被驱动的关系,所以Join本身涉及到的多个表之间的数据读取一般都会存在一个顺序关系。但是排序分页就不太一样了,排序分页的数据源基本上可以说是一个表(或者一个结果集),本身并不存在一个顺序关系,所以在从多个数据源取数据的过程是完全可以并行的。这样,排序分页数据的取数效率我们可以做的比跨库Join更高,所以带来的性能损失相对的要更小,在有些情况下可能比在原来未进行数据切分的数据库中效率更高了。当然,不论是跨节点Join还是跨节点排序分页,都会使我们的应用服务器消耗更多的资源,尤其是内存资源,因为我们在读取访问以及合并结果集的这个过程需要比原来处理更多的数据。

  分析到这里,可能很多朋友会发现,上面所有的这些问题,我给出的建议基本上都是通过应用程序来解决。大家可能心里开始犯嘀咕了,是不是因为我是DBA,所以就很多事情都扔给应用架构师和开发人员了?

  其实完全不是这样,首先应用程序由于其特殊性,可以非常容易做到很好的扩展性,但是数据库就不一样,必须借助很多其他的方式才能做到扩展,而且在这个扩展过程中,很难避免带来有些原来在集中式数据库中可以解决但被切分开成一个数据库集群之后就成为一个难题的情况。要想让系统整体得到最大限度的扩展,我们只能让应用程序做更多的事情,来解决数据库集群无法较好解决的问题。

  小编结语

  通过数据切分技术将一个大的MySQLServer切分成多个小的MySQLServer,既解决了写入性能瓶颈问题,同时也再一次提升了整个数据库集群的扩展性。不论是通过垂直切分,还是水平切分,都能够让系统遇到瓶颈的可能性更小。尤其是当我们使用垂直和水平相结合的切分方法之后,理论上将不会再遇到扩展瓶颈了。

赞(25)
踩(0)
分享到:
华为认证网络工程师 HCIE直播课视频教程