你好,欢迎进入江苏优软数字科技有限公司官网!

诚信、勤奋、创新、卓越

友好定价、专业客服支持、正版软件一站式服务提供

13262879759

工作日:9:00-22:00

数据库分库分表解决方案汇总

发布时间:2023-06-07

浏览次数:0

点击上方红色“程序员DD”,选择“设为星标”

回复“资源”获取独家学习资料!

intellij idea 数据库关系图_intellij idea 数据库关系图_intellij idea安装教程

作者|

来源|.com//p/.html

1、数据切分

关系型数据库本身比较容易成为系统困境,单机的存储容量、连接数、处理能力都是有限的。 当单表数据量达到1000W或100G时,由于查询维度较多,即使添加从库,优化索引,进行多次操作时,性能仍会有较大幅度的提升。 这时候就要考虑拆分了。 拆分的目的是减轻数据库的负担,缩短查询时间。

数据库分布的核心内容无非就是数据的切分(),以及切分后数据的定位与整合。 数据切分就是将数据分散存储在多个数据库中intellij idea 数据库关系图,使单个数据库中的数据量变小。 通过扩展主机数量,减缓单个数据库的性能问题,从而达到提高数据库运行性能的目的。

数据切分按其切分类型可分为垂直(水平)切分和水平(垂直)切分两种形式。

1.纵向(横向)分割

垂直分片有两种常见类型:数据库垂直分片和表垂直分片。

垂直分片是基于业务耦合,将关联度低的不同表存储在不同的数据库中。 这种做法类似于将一个大系统拆分成多个小系统,按照业务分类独立定义。 类似于“微服务治理”的方式,每个微服务使用一个单独的数据库。 如图所示:

intellij idea 数据库关系图_intellij idea安装教程_intellij idea 数据库关系图

垂直表划分是基于数据库中的“列”。 如果某个表的数组比较多,可以创建一个扩展表,将不常用或者数组厚度较大的数组拆分到扩展表中。 在数组很多的情况下(比如一个大表有100多个数组),通过“大表拆成小表”更容易开发和维护,也可以防止跨页问题。 MySQL底层是通过数据页来存储的。 记录占用的空间比会话引起的页面传播更多,从而导致额外的性能开销。 另外,数据库以行为单位将数据加载到显存中,这样表中主键的宽度越短,访问频率越高,显存可以加载的数据越多,命中率越高,并且减少了c盘的IO,从而提高了数据库的性能。

intellij idea 数据库关系图_intellij idea 数据库关系图_intellij idea安装教程

垂直拆分的优点:

缺点:

2.横向(纵向)切分

当应用不能垂直分片,或者分片后数据量巨大,存在单库读写和存储性能问题时,就需要水平分片。

水平切分分为数据库分表和分库分表。 根据表中数据的内在逻辑关系,将同一张表根据不同的条件分布到多个数据库或多个表中。 每个表只包含部分数据,然后减少单个表的数据量,达到分布的效果。 如图所示:

intellij idea 数据库关系图_intellij idea 数据库关系图_intellij idea安装教程

库内分表只是解决了单表数据量过大的问题,并没有将表分布到不同机器的库中。 所以对于减轻MySQL数据库的压力帮助不大。 你还在争同一台数学机 CPU、内存、网络IO最好分库分表解决。

水平拆分的优点:

缺点:

水平拆分后,同一张表会出现在多个数据库/表中,每个数据库/表的内容都不一样。 几种典型的数据分片规则是:

1.按取值范围

按时间间隔或ID间隔分段。 例如:将不同月份甚至不同日期的数据按照日期分散到不同的库中; 将1~9999条记录分配到第一个库,将10000~20000条记录分配到第二个库,以此类推。 从某种意义上说,在个别系统中使用的“冷热数据分离”,将一些使用较少的历史数据迁移到其他库中,只提供业务功能的热数据查询,也是一种类似的做法。

这样做的好处是:

缺点:

intellij idea 数据库关系图_intellij idea安装教程_intellij idea 数据库关系图

2.根据值取模

通常采用散列取模的切分方法。 比如把表按照cusno数组分成4个bank,余数为0的放在第一bank,余数为1的放在第二bank。 有点推动。 这样,同一个用户的数据就会分散在同一个库中。 如果查询条件有cusno数组,可以明确定位到对应的库进行查询。

优势:

缺点:

intellij idea 数据库关系图_intellij idea安装教程_intellij idea 数据库关系图

2.分库分表带来的问题

分库分表可以有效缓解单机单库带来的性能困境和压力,突破网络IO、硬件资源、连接数的困境,但也带来了一些问题。 下面介绍技术挑战和相应的解决方案。

1.交易一致性问题

分布式事务

当更新的内容同时分布在不同的库中时,必然会导致跨库事务问题。 跨分片交易也是分布式交易,没有简单的解决方案。 通常可以使用“XA契约”和“两阶段提交”进行处理。

分布式事务可以最大化数据库操作的原子性。 但提交交易时需要多个节点协同,延误了提交交易的时间点,延长了交易的执行时间。 这会增加事务在访问共享资源时发生冲突或死锁的可能性。 随着数据库节点的增加,这些趋势会越来越严重,从而成为系统在数据库层面水平扩展的桎梏。

最终一致性

对于性能要求高但一致性要求低的系统,系统的实时一致性往往不是很关键,只要在允许的时间段内达到最终一致性即可,可以采用事务补偿的方式。 不同于在执行过程中出现错误后立即回滚事务的形式,事务补偿是一种事后检测和修复的措施。 一些常见的实现方式包括:数据的对账校验、基于日志的比对、定期与标准数据源比对同步等。 交易补偿也要结合业务系统来考虑。

2.跨节点关联查询加入问题

拆分前,系统中很多列表和详情页需要的数据,都可以通过来完成。 拆分后,数据可能分布在不同的节点上。 这时候join带来的问题就比较麻烦了。 考虑到性能,尽量避免使用连接查询。

解决这个问题的一些技巧:

1)全局表

全局表也可以看作是“数据字典表”,是系统中所有模块都可能依赖的一些表。 为了防止跨库join查询,可以在每个库中保存一份这样的表。 这种数据一般很少改动,所以不怕一致性问题。

2)阵列冗余

典型的反范式设计使用空间换取时间并避免连接查询以提高性能。 例如:保存订单表时,也会保存一份冗余的副本,这样查询订单明细时就不需要查询“卖家用户表”了。

但是,这些技术也适用于有限的场景,更适用于依赖数组较少的情况。 而且冗余数组的数据一致性也很难保证。 就像前面订单表的反例,卖家变更后,是否需要在历史订单中同步更新? 这个也要结合实际业务场景考虑。

3)数据组装

在系统层面,查询分为两次。 在第一次查询的结果中找到关联数据id,然后根据id进行第二次请求,获取关联数据。 最后将得到的数据组装成一个数组。

4) ER碎片化

在关系型数据库中,如果能够先确定表之间的关系,将这些相关表的记录存储在同一个分片上,那么就可以更好地防止跨分片连接的问题。 在1:1或者1:n的情况下,一般是根据主表的ID字段来划分。 如右图所示:

intellij idea 数据库关系图_intellij idea 数据库关系图_intellij idea安装教程

这样中的订单表和订单明细表就可以通过偏相关的方式进行查询,在上也是如此。

3、跨节点分页、排序、功能问题

跨节点、多数据库查询时,会出现limit分页、排序等问题。 分页需要按照指定的数组进行排序。 当排序数组为分片数组时,通过分片规则更容易定位到指定的分片; 当排序数组不是分片数组时,比较复杂。 需要先对不同分片节点上的数据进行排序返回,然后对不同分片返回的结果集进行汇总重新排序,最后返回给用户。 如图所示:

intellij idea安装教程_intellij idea 数据库关系图_intellij idea 数据库关系图

上图中只取了第一页的数据,对性能影响不大。 而如果获取的page数量很大,情况就复杂很多,因为每个shard节点中的数据可能是随机的。 为了排序的准确性,需要对所有节点的前N页数据进行排序进行合并,最后进行整体排序,这样的操作会消耗大量的CPU和显存资源,所以页数越大,系统性能越差。

在使用Max、Min、Sum、Count等函数进行估算时,也需要先在每个分片上执行相应的函数,然后对每个分片的结果集进行汇总和重新估算,最后返回结果。 如图所示:

intellij idea 数据库关系图_intellij idea安装教程_intellij idea 数据库关系图

4.避免全局字段重复的问题

在分库分表环境下,由于表中的数据同时存在于不同的数据库中,平时使用的自减字段值就没有用了,分区数据库自生成ID不能保证全局唯一性。 因此需要单独设计全局字段,防止跨库字段重复。 有一些常见的字段生成策略:

1) UUID

UUID标准方法包含32个16位补码,分为5段,方法为8-4-4-4-12的36个字符,如:-e29b-41d4-a716-

UUID字段是最简单的方案,本地生成,性能高,无网络时长。 但是缺点也很明显,因为UUID很长,会占用很多存储空间; 另外,作为字段建立索引,根据索引进行查询,也会有性能问题。 其次,UUID的乱序会导致数据位置的频繁变化。 造成分页。

2)结合数据库维护字段ID表

在数据库中建表:

CREATE TABLE `sequence` (
  `id` bigint(20unsigned NOT NULL auto_increment,
  `stub` char(1NOT NULL default '',
  PRIMARY KEY  (`id`),
  UNIQUE KEY `stub` (`stub`)
ENGINE=MyISAM;

存根数组设置为唯一索引,同一个存根值在表中只有一条记录,可以同时为多个表生成全局ID。 表的内容,如下:

+-------------------+------+
| id | stub |
+-------------------+------+
| 72157623227190423 | a |
+-------------------+------+

改用存储引擎以获得更高的性能。 使用表级锁,表的读写是串行的,不用担心并发时读取同一个ID值两次。

当需要全局唯一的64位ID时,执行:

REPLACE INTO sequence (stub) VALUES ('a');
SELECT LAST_INSERT_ID();

这两句是水平的,()必须和into连接同一个数据库才能得到刚刚插入的新ID。

使用into而不是into是为了避免表中的行数过多,不需要定期清除。

这个方案比较简单,但是它的缺点也很显着:存在单点问题,对DB的依赖性强。 当DB异常时,整个系统不可用。 配置主从可以降低可用性,但是当主库挂掉,主从切换时,特殊情况下无法保证数据的一致性。 另外,性能困境仅限于单个MySQL的读写性能。

团队使用的一个字段生成策略和之前的表方案类似,但是更好的解决了单点和性能困境的问题。

本方案的总体思路是:搭建2台以上的服务器进行全局ID的生成,每台服务器上只部署一个数据库,每个数据库都有一张表用于记录当前的全局ID。 ID在表中递减的步长为数据库个数,起始值依次错开,这样ID的生成可以hash到每个数据库。 如右图所示:

intellij idea 数据库关系图_intellij idea安装教程_intellij idea 数据库关系图

ID由两个数据库服务器生成,设置不同的值。 第一个的起始值为1,步长每次减2,另一个的起始值为2,步长每次减2。 结果,第一台机器生成的ID都是质数(1,3,5,7...),第二台机器生成的ID都是质数(2,4,6,8.. .).

这些方案在两台机器上平均分配生成 ID 的压力。 同时提供系统容错能力。 如果第一台机器出现错误,可以手动切换到第二台机器获取ID。 但是也有几个缺点:系统中增加机器,横向扩展比较复杂; 每获取一个ID,都要对DB进行一次读写,对DB的压力还是很大的,只能通过堆机来提升性能。

大家可以在方案的基础上继续优化,使用batch的方式来降低数据库的写入压力,每次获取一个范围的ID号段,用完再去数据库中获取,可以大大降低数据库的压力。 如右图所示:

intellij idea 数据库关系图_intellij idea安装教程_intellij idea 数据库关系图

仍然使用两个DB来保证可用性,数据库中只存储当前最大的ID。 ID生成服务每次批量拉取6个ID,先改成5个,应用访问ID生成服务时不需要访问数据库,从号码段开始依次分配ID从0到5缓存。 这种ID发放后,会改为11,最后一次发放6~11的ID即可。 这样一来,数据库的压力增加到原来的1/6。

3)分布式自增ID算法

该算法解决分布式系统生成全局ID的需求,生成64位Long数。 这些组件是:

这样做的用处是:微秒数低,生成的ID整体按照时间趋势递增; 不依赖第三方系统,稳定性和效率高。 理论上QPS约为409.6w/s (1000*2^12) ,但整个分布式系统不会形成ID冲突; bits可以根据自己的业务灵活分配。

缺点是强烈依赖于机器时钟。 如果直接拨时钟,可能会导致重复生成ID。

综上所述,结合数据库和唯一ID方案,可以参考业界比较成熟的方案:Leaf-美团点评分布式ID生成系统,并考虑高可用、容灾、分布式时钟等问题。

5. 数据迁移和扩容问题

当业务快速发展,面临性能和存储困难时,就会考虑分片设计。 这时候就不可避免地要考虑历史数据迁移的问题。 通常的做法是先读取历史数据,然后按照指定的分片规则将数据写入各个分片节点。 据悉,还需要根据当前数据量、QPS、业务发展速度进行容量规划,预估大概需要的分片数量(一般建议单表数据量单个分片上不应超过 1000W)。

如果使用数值范围分片,只需要增加节点扩容,不需要迁移分片数据。 如果采用数值取模分片,考虑后期扩容问题相对麻烦。

3. 何时考虑细分

下面描述什么时候需要考虑数据切分。

1.尽量不要分

并不是所有的表都需要拆分,主要看数据下降的速度。 切分会在一定程度上增加业务的复杂度。 数据库不仅承载数据的存储和查询,辅助业务更好地实现需求是其重要任务之一。

除非万不得已,否则不要使用分库分表的大招,防止“过度设计”和“过早优化”。 分库分表之前,不要为了分而分,先量力而行,比如:升级硬件,升级网络,读写分离,索引优化等。数据达到单表的困境,考虑分库分表。

2、数据量过大,正常运维影响业务接入

这里所说的运维是指:

1)数据库备份,如果单表太大,备份需要大量的C盘IO和网络IO。比如1T的数据,当网络传输占用50MB时,需要20000秒才能完成传输,而且整个过程风险比较高

2)当对大表进行DDL更改时,MySQL会锁住整张表,时间会很长,而且这段时间业务不能访问这张表,影响很大。 如果使用pt---,在使用过程中会创建触发器和影子表,耗时较长。 在此操作期间,它被计为风险时间。 拆分数据表并减少总数可以帮助降低这种风险。

3)大表会被频繁访问和更新,所以更容易出现锁等待。对数据进行分段,以空间换取时间,变相降低访问压力

3、随着业务发展,个别阵列需要垂直拆分

作为一个反例,如果项目一开始设计的user表是这样的:

id                   bigint #用户的ID
name varchar #用户的名字
last_login_time datetime #最近登录时间
personal_info text #私人信息
..... #其他信息字段

在项目初期,这些设计满足简单的业务需求,便于快速迭代开发。 业务高速发展时,用户数量从10万猛增到10亿,用户非常活跃。 每次登录都会更新数组,导致用户表不断更新,压力很大。 而其他数组:id,name,没有变化或者很少更新。 这时候,从业务的角度来说,就需要将它们拆分出来,新建一张表。

属性更新和查询频率较低,文本数组占用空间过大。 这时候就需要垂直拆分表格了。

4、数据量快速下降

随着业务的快速发展,单表的数据量会不断减少。 当性能接近困境时,就要考虑水平切分,做分库分表。 这时候就需要选择合适的切分规则,提前预估数据容量。

5. 安全性和可用性

不要把猪肉放在一个篮子里。 在业务层面,垂直切分将不相关业务的数据库分开。 由于每个业务的数据量和访问量不同,不可能通过将数据库与一个业务挂钩来牵扯到其他业务。 通过水平分片,当一个数据库出现问题时,不会影响到100%的用户,每个数据库只承载部分业务数据,从而提高整体的可用性。

四、案例分析 1、以用户为中心的业务场景

用户中心是一个很常见的业务,主要提供用户注册、登录、查询/更改等功能,其核心表是:

User(uid, login_name, passwd, sex, age, nickname)

uid为用户ID,字段

login_name, passwd, sex, age, nickname, 用户属性

任何脱离业务的架构设计都是耍流氓。 分库分表之前,需要梳理一下业务场景需求:

用户端:前台访问,访问量大,需要保证高可用和一致性。 有两种主要类型的需求:

运营端:后台接入intellij idea 数据库关系图,支持运营需求,根据年龄、性别、登录时间、注册时间等进行分页查询。属于访问量小、可用性和一致性要求不高的内部系统。

2.横向分割法

当数据量越来越大时,就需要对数据库进行水平切分。 上述的分割方法包括“按取值范围”和“按取值​​取模”。

“按取值范围”:以字段uid为依据,将数据按uid范围横向划分到多个数据库中。 例如:user-db1存储uid范围为0~1000w的数据,user-db2存储uid范围为1000w~的数据。

优点是:扩容简单,如果容量不够,缩减新的db即可。

缺点是:请求量不均,通常新注册用户的活跃度比较高,所以new user-db2的负载会比user-db1高,导致服务器利用率不均衡

“按值取模”:也是基于字段uid来定义,根据uid取模的值将数据横向分到多个数据库中。 例如:user-db1存储uid为模1的数据,user-db2存储uid为模0的uid数据。

优点是:数据量和请求量分布均匀

缺点是:扩容麻烦。 当容量不够的时候,就需要降低db。 需要考虑数据的平滑迁移。

3.非uid查询方式

水平切分后,可以很好的满足通过uid查询的需求,可以直接路由到具体的数据库。 例如,对于非 uid 查询,您不知道访问哪个库。 这时候就需要遍历所有的库,性能会提高很多。

对于用户端,可以采用“构造非uid属性与uid的映射关系”的方案; 对于运营端,可以采用“前后台分离”的方案。

3.1. 建立非uid属性到uid的映射关系

1)映射关系

例如,如果不能直接定位到数据库,可以建立一个→uid映射关系,存储在索引表或缓存中。 访问时先通过映射表查询对应的uid,再通过uid定位到具体的库。

映射表只有两列,可以承载很多数据。 当数据量太大时,也可以水平拆分映射表。 这种kv格式的索引结构可以使用缓存来优化查询性能,而且映射关系不会频繁变化,缓存命中率会很高。

2)遗传法

分库基因:如果使用uid将数据库分成8个库,使用uid%8的方式进行路由,则uid的后3位将决定这一行的User数据落入哪个库. 那么这3位就可以看成是一个分库基因。

其中的映射关系需要额外存储映射表。 通过非 uid 数组查询时,需要多一次数据库或缓存访问。 如果要去除冗余存储和查询,可以将f函数得到的基因作为uid分库基因。 生成uid时,参考上面介绍的分布式唯一ID生成方案,加上最后3位value=f()。 查询时,只需要估计f()%8的值,就可以定位到具体的库。 但是,这需要提前进行容量规划,预估未来几年的数据量需要分成多少个数据库,并预留一定的分库基因。

intellij idea 数据库关系图_intellij idea 数据库关系图_intellij idea安装教程

3.2. 前景和背景分离

对于用户端,主要需求是单行查询。 需要建立一个/phone/email到uid的映射关系,可以解决这个数组的查询问题。

对于运营端,批量分页、各种条件的查询非常多。 此类查询预估量大,返回数据量大,对数据库性能消耗大。 此时,如果同一批服务或数据库共享给用户侧,可能会因为后台请求量少,占用大量数据库资源,导致用户侧访问性能下降或超时.

这类业务最好采用“前后台分离”的方案。 运营端后台业务抽取一个独立的和db来解决与前台业务系统的耦合。 因为运营端对可用性和一致性要求不高,所以不需要访问实时库,而是通过异步和同步数据访问运营库。 在数据量大的情况下,也可以使用ES搜索引擎或者Hive来满足后台复杂的查询方式。

5.支持分库分表中间件

站在巨人的右臂上,能省不少力气。 目前已经有一些成熟的分库分表开源方案:

6.参考

[1] 数据库分布式架构素养——分库分表(及对建行核心系统适用性的思考)

[2] 分库分表的思想

[3] 横向分库分表的关键步骤及可能出现的问题

[4] 从原理、方案、策略和难点谈分库分表

[5] Leaf——美团点评分布式ID生成系统

[6] 建筑师之路公众号

感谢阅读,原创不易,喜欢就​​点【在看】或【转发同学圈】吧,这就是写作最大的动力。

本文由转换工具发布

关注我,回复“家群”加入各种话题讨论群

intellij idea 数据库关系图_intellij idea安装教程_intellij idea 数据库关系图

我读过了

如有侵权请联系删除!

13262879759

微信二维码