分库分表主要有两种方式:水平拆分和垂直拆分
分库的意思就是把数据表按照业务不同分成不同的库,可以减少单个库的压力
例如按照业务将不同模块的数据放在不同的库中。
分表的意思就是将一个表中的数据分成多个表按照一定的逻辑拆分成多个表
从而减少单库的压力。水平拆分简单理解起来就是将一个表中数据分成多个结构
相同的表,垂直拆分就是将一个表分成多个关联的小表,组合在一起就是整个表。
水平拆分的意思,就是把一个表的数据给弄到多个库的多个表里去,
但是每个库的表结构都一样,只不过每个库表放的数据是不同的,
所有库表的数据加起来就是全部数据。水平拆分的意义,就是将数据均匀放更多的库里,
然后用多个库来扛更高的并发,还有就是用多个库的存储容量来进行扩容。
例如按照分组将同一个组的数据放在同一个库的表中,每个库的表结构都是一样的。
垂直拆分的意思,就是把一个有很多字段的表给拆分成多个表,或者是多个库上去。每个
库表的结构都不一样,每个库表都包含部分字段。一般来说,会将较少的访问频率很高的
字段放到一个表里去,然后将较多的访问频率很低的字段放到另外一个表里去。因为数据
库是有缓存的,你访问频率高的行字段越少,就可以在缓存里缓存更多的行,性能就越好。
这个一般在表层面做的较多一些。
分库分表的方式主要有两种:
一种是按照 range 来分,就是每个库一段连续的数据,这个一般是按比如时间范围来的,
但是这种一般较少用,因为很容易产生热点问题,大量的流量都打在最新的数据上了。
或者是按照某个字段 (例如根据客户编码,或者其他代表编码)hash 一下均匀分散,保证可以关联查询,这个较为常用。
range 来分,好处在于说,扩容的时候很简单,因为你只要预备好,给每个月都准备一
个库就可以了,到了一个新的月份的时候,自然而然,就会写新的库了;缺点,但是大部
分的请求,都是访问最新的数据。实际生产用 range,要看场景。
hash 分发,好处在于说,可以平均分配每个库的数据量和请求压力;坏处在于说扩容起来
比较麻烦,会有一个数据迁移的过程,之前的数据需要重新计算 hash 值重新分配到不同的
库或表。一般是按照id主键与分的表的个数取余放在不同的表中,可以用雪花算法
分库之后查询时一般把需要查询的都查出来放在内存中组装需要的对象不能用sql直接关联查询
无论大家选择使用哪一种解决方式,总体设计思路基本上都不应该会有不论什么变化。那就是通
过数据的垂直和水平切分,增强数据库的总体服务能力,让应用系统的总体扩展能力尽可能的提升。
扩展方式尽可能的便捷。
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还是跨节点排序分页。都会使我们的应用server消耗很多其它的资源,尤其是内存资源,由于我们在读取訪问以及合并结果集的这个过程须要比原来处理很多其它的数据。
分析到这里,可能非常多读者朋友会发现,上面全部的这些问题,我给出的建议基本上都是通过应用程序来解决。大家可能心里開始犯嘀咕了。是不是由于我是DBA,所以就非常多事情都扔给应用架构师和开发人员了?
事实上全然不是这样,首先应用程序由于其特殊性。能够非常easy做到非常好的扩展性,可是数据库就不一样。必须借助非常多其它的方式才干做到扩展。并且在这个扩展过程中,非常难避免带来有些原来在集中式数据库中能够解决但被切分开成一个数据库集群之后就成为一个难题的情况。
要想让系统总体得到最大限度的扩展,我们仅仅能让应用程序做很多其它的事情。来解决数据库集群无法较好解决的问题。
补充:
分库分表中主键id生成的方式一般采用雪花算法如下是美团的开源雪花算法:
美团团队雪花算法:https://tech.meituan.com/2019/03/07/open-source-project-leaf.html