当数据库的数据量太大到一定程度时,我们可以分库分表.那么基于什么原则和方法来拆分,这就是本文要讲的内容。图片来自Pexels数据库瓶颈。无论是IO瓶颈还是CPU瓶颈,最终都会导致数据库的活跃连接数增加,逼近甚至达到数据库能够承载的活跃连接数的阈值。从业务服务的角度来看,可用的数据库连接很少或者没有,然后就可以想象了(并发、吞吐量、崩溃)。IO瓶颈:第一种:磁盘读IO瓶颈,热数据太多,数据库缓存放不下,每次查询都会产生大量IO,降低查询速度→分库垂直分表。第二种:网络IO瓶颈,请求数据过多,网络带宽不足→分库。CPU瓶颈:第一类:SQL问题:例如SQL包括join、groupby、orderby、非索引字段条件查询等,增加CPU运算→SQL优化,建立合适的索引,在其中进行业务计算业务服务层。第二种:单表数据量过大,查询时扫描行过多,SQL效率低,增加CPU运算→水平分表的操作。分库、分表水平分库的概念:以字段为基础,按照一定的策略(hash、range等),将一个数据库中的数据拆分到多个数据库中。结果:各库结构相同,各库数据不同。没有交集。业务归属来源于垂直分馆的案例。分析:库多了,IO和CPU的压力自然成倍增加。水平分表的概念:以字段为基础,按照一定的策略(hash、range等),将一个表中的数据拆分到多个表中。结果:各表结构相同。每个表的数据不一样,没有交集,所有表的并集就是全量数据。场景:系统绝对并发没有增加,但是单表数据量过大,影响SQL效率,增加CPU负担,成为瓶颈。可以考虑水平分表。分析:单表数据量减少,单条SQL执行效率高,自然减轻CPU负担。数据库垂直划分的概念:以表为基础,根据不同的业务属性,将不同的表拆分到不同的库中。结果:每个库的结构都不一样。每个库的数据都不一样,没有交集。所有库的联合就是全量数据。场景:系统的绝对并发增加,可以抽象出一个单独的业务模块。分析:至此,基本可以面向服务了。比如:随着业务的发展,公共配置表、字典表等越来越多,这时候可以把这些表拆分成一个单独的库,甚至可以面向服务。此外,随着业务的发展,已经孵化出了一套商业模式。这时候可以把相关的表拆解成一个单独的库,甚至可以面向服务。垂直分表的概念:以字段为基础,将表中的字段根据字段的活跃度拆分成不同的表(主表和扩展表)。结果:每张表的结构都不一样。每个表的数据都不一样。一般来说,每个表的字段至少有一个交集列,通常是主键,用来关联数据。所有表的并集就是全量数据。场景:系统的绝对并发没有增加。表中记录不多,但是字段很多,热点数据和非热点数据在一起。单行数据所需的存储空间较大,从而减少了缓存在数据库中的数据行数。查询时回头读磁盘数据会产生大量的随机读IO,造成IO瓶颈。分析:可以使用列表页和详情页来帮助理解。垂直分表的原理是将热点数据(可能被频繁查询的数据)放在一起作为主表,非热点数据放在一起作为扩展表,这样可以缓存更多的热点数据,从而减少随机读IO.拆解后,如果要获取所有的数据,需要关联两个表来获取数据。但是切记不要使用Join,因为Join不仅会增加CPU负担,还会把两个表耦合在一起(必须在一个数据库实例上)。关联数据应该在Service层进行,分别获取主表和分表的数据,然后使用关联字段关联所有数据。常用的分库分表工具有:Sharding-JDBC(当当网)TSharding(蘑菇街)Atlas(奇虎360)Cobar(阿里巴巴)MyCAT(基于Cobar)Oceanus(58同城)Vitess(谷歌)各种工具优劣自查分库分表带来的问题分库分表可以有效缓解单机单表带来的性能瓶颈和压力,突破网络IO瓶颈,硬件资源,以及连接数,也会带来一些问题,下面会介绍这些问题和解决方法。事务一致性问题①分布式事务当更新的内容同时存在于不同的数据库中时,必然会导致跨库事务问题。跨分片交易也是分布式交易,没有简单的解决方案。一般可以使用“XA协议”和“两阶段提交”进行处理。分布式事务可以最大化数据库操作的原子性。但是,在提交事务时需要协调多个节点,延误了提交事务的时间点,延长了事务的执行时间,增加了事务访问共享资源时发生冲突或死锁的概率。随着数据库节点的增多,这种趋势会越来越严重,从而成为系统在数据库层面横向扩展的桎梏。②最终一致性对于那些对性能要求高但一致性要求不高的系统,往往不需要系统的实时一致性,只要在允许的时间段内达到最终一致性即可,可以采用事务补偿。不同于交易执行过程中出现错误立即回滚的方式,交易补偿是一种事后检查和修复的措施。一些常见的实现方式包括:数据的对账校验、基于日志的比对、定期与标准数据源同步比对等。通过Join,但是经过切分之后,数据可能会分布在不同的节点上。这时候Join带来的问题就比较麻烦了,考虑到性能,尽量避免使用Join查询。一些解决方案:①全局表全局表也可以看作是“数据字典表”,是系统中所有模块都可能依赖的一些表。为了避免libraryJoin查询,这样的表可以在每个数据库中保存一份。这些数据通常很少被修改,因此无需担心一致性问题。②字段冗余是典型的反范式设计,以空间换时间,以性能避免Join查询。比如order表在保存userId的时候,同时保存了一份userName的冗余副本,这样用户名userName顺表查询订单明细就可以找到了,就不用去查询买家的user表了.但是,这种方法适用的场景有限。比较适用于依赖字段比较少,冗余字段的一致性难以保证的情况。③在系统服务层进行数据组装,查询分为两次。第一次查询的结果集找到关联数据id,然后根据id发起者的第二次请求获取关联数据,最后对获取的结果进行字段拼装。这是一种比较常用的方法。④ERsharding关系型数据库中,如果表(如订单表和订单明细表)之间的关系已经确定,那些有关系的表记录存储在同一个分片上,那么可以更好的避免cross-shardJoin的问题。Join可以在分片中执行。在1:1或1:n的情况下,主键通常根据主表的ID进行拆分。跨节点分页、排序、函数问题跨节点、跨数据库查询时,会出现限制分页、排序排序等问题。分页需要根据指定的字段进行排序。当排序字段为分页字段时,更容易通过分片规则定位到指定的分片;当排序字段不是分片字段时,它变得更复杂。需要先对不同分片节点中的数据进行排序返回,然后对不同分片返回的结果集进行汇总和重新排序。最后返回给用户如下图所示:上图只取了第一页的数据,对性能影响不大。但是如果获取的页数很大,情况就复杂很多。由于每个分片节点中的数据可能是随机的,为了排序的准确性,需要对所有节点的前N页数据进行排序合并,最后进行整体排序。这样的操作会消耗CPU和内存资源。所以页数越大,系统性能就越差。在使用Max、Min、Sum、Count等函数进行计算时,也需要先在每个分片上执行相应的函数,然后再汇总每个分片的结果集再进行计算。全局主键规避问题在分库分表环境下,由于表中的数据同时存在于不同的数据库中,通常使用的主键值自增长将无用,而分区数据库自生成的ID不能保证全局唯一。因此需要单独设计全局主键,避免跨数据库主键重复。下面是一些策略:①UUIDUUID的标准形式是32个十六进制数,分为5段,形式为8-4-4-4-12的36个字符。UUID是最简单的解决方案。本地生成,性能高,不消耗网络时间。但是,它也有明显的缺点,占用大量的存储空间。另外,建立索引作为主键并根据索引进行查询存在性能问题,尤其是在InnoDb引擎下,UUID的乱序会导致索引位置频繁变化,导致分页。②结合数据库维护主键ID表在数据库中创建sequence表:CREATETABLE`sequence`(`id`bigint(20)unsignedNOTNULLauto_increment,`stub`char(1)NOTNULLdefault'',PRIMARYKEY(`id`),UNIQUEKEY`stub`(`stub`))ENGINE=MyISAM;存根字段设置为唯一索引,同一个存根值在序列表中只有一条记录,可以同时作为多个表的全局ID。通过使用MyISAM引擎而不是InnoDb实现了更高的性能。MyISAM使用表锁,表的读写是串行的,所以并发时不用担心读取同一个ID两次。当需要全局唯一ID时,执行:REPLACEINTOsequence(stub)VALUES('a');SELECTLAST_INSERT_ID();这种方案比较简单,但缺点也很明显:存在单点问题,对DB的依赖性强。当DB异常时,整个System不可用。配置主从可以提高可用性。另外,性能瓶颈仅限于单个MySQL的读写性能。另一种主键生成策略,类似于序列表方案,更好的解决了单点和性能瓶颈问题。本方案的总体思路是:建立2台以上的服务器进行全局ID的生成,每台服务器上只部署一个数据库,每个数据库都有一个序列表记录当前的全局ID。表中增长的步长就是库的个数,起始值依次错开,这样ID生成就可以hash到每个库。该方案将生成ID的压力平均分配到两台机器上,同时提供系统容错能力。如果第一台机器出现错误,可以自动切换到第二台机器获取ID。但是也有几个缺点:系统增加机器,横向扩展比较复杂;每次获取ID都要读一次DB,对DB的压力还是很大的,只能通过堆机来提升性能。③Snowflake分布式自增ID算法Twitter的Snowfalke算法解决了分布式系统生成全局ID的需求,生成64位Long数。组成部分如下:第一位未使用。接下来的41位是毫秒级的时间,41位的长度可以代表69年的时间。5位datacenterId,5位workerId。10位长度支持最多部署1024个节点。最后12位以毫秒为单位计数,12位计数序列号支持每个节点每毫秒产生4096个ID序列。数据迁移和扩容当业务快速发展,面临性能和存储瓶颈时,就会考虑分片设计。这时候就不可避免地要考虑历史数据的迁移。一般的做法是先读取历史数据,然后按照指定的分片规则将数据写入各个分片节点。另外需要根据当前数据量QPS和业务发展速度进行容量规划,计算大概需要的分片数量(一般建议单个分片中单表的数据量不应超过1000W)。什么时候考虑分库分表①如果可以不分表,不是所有的表都需要分表,主要看数据的增长速度。分割在一定程度上增加了业务的复杂性。除非万不得已,否则不要使用分库分表的“大招”,避免“过度设计”和“过早优化”。分库分表前,尽量优化:升级硬件、升级网络、读写分离、索引优化等,当数据量达到单表瓶颈时,考虑分库分表-桌子。②数据量过大,正常运维影响业务接入。这里的运维是指:数据库备份,如果单表太大,备份需要大量的磁盘IO和网络IO。对大表做DDL时,MySQL会锁住整张表,时间会比较长,而且这段时间业务不能访问这张表,影响很大。大表被频繁访问和更新,更容易出现锁等待。③随着业务的发展,部分领域需要垂直拆分。这里我就不举例了。实际业务中可能会遇到。一些不经常访问或不经常更新的字段应该从大表中分离出来。④数据量快速增长随着业务的快速发展,单表的数据量会不断增长。当性能接近瓶颈时,就要考虑水平切分,做分库分表。参考文章:https://www.cnblogs.com/butterfly100/p/9034281.htmlhttps://www.cnblogs.com/littlecharacter/p/9342129.html作者:坚持就是胜利编辑:陶家龙来源:https://juejin.im/post/5dc77a9451882559465e390b
