欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

数据库分表分库及分表分库带来的问题

程序员文章站 2024-03-17 14:04:04
...

相关内容:数据库分区:MySQL分区

一、分表

数据库分表可以解决单表海量数据的查询性能问题。
分表分为垂直分表和水平分表。

1、垂直分表

垂直分表一般是分割比较大的字段或者访问频率低的字段,将这部分字段数据剖分出来作为一张表。简单来说,就是把大的和不常用的与常用的分离开。
举个例子:(1)假设一个博客网站,其文章表table_artical(id,user_id,title,abstract,content,create_time),显然content字段对应的数据很长,当只需要查询文章列表时,content将会大大影响table_artical的读取速度,如果将content字段再划分为一个表,table_artical_detail(article_id,content),原表变为table_artical(id,user_id,title,abstract, create_time),此时再去查询文章列表就无需访问content字段,如果想获取文章详情,通过article_id去table_articals_detail查询即可。
数据库分表分库及分表分库带来的问题
(2)常用的订单表table_order,经常还有一张订单详情表table_order_detail,就是将order不常用的字段分离出去。

优点:(1)能够使行数据变小,在查询时减少I/O的次数;
(2)能够简化表的结构易于维护。
缺点:(1)主键出现冗余,需要管理冗余列;
(2)查询所有数据需要JOIN操作;
(3)会让事务变得更加复杂。

2、水平分表

水平分表是将数据表按行进行划分,一般表超过500W行或者10GB或者通过垂直分表后,表格依然很大时,就需要对表进行水平分表,水平分表后要保证各表的数据量相当。
数据库分表分库及分表分库带来的问题

分割标准:

(1)一般采取的分表策略是对id取模,若不是整数,也可经过hash计算取整后取模。
(2)用户表也可以根据用户手机号进行分表,比如user138、user150、user155等,每个号码段作为一张表;或者其他具有明显划分的依据,比如学号,student2018、student2019、student2020等。
(3)对于订单表这类表,可以采用时间进行水平分表。

优点:(1)支持非常大的数据量存储;
(2)应用程序端整体架构改动相对较少。
缺点:(1)分片事务难以解决,跨界点Join性能较差,逻辑复杂;
(2)水平拆分会给应用增加复杂度,它通常在查询是需要多个表名,查询所有数据需要union操作;对于表的统计信息,可以通过增加一张表来存储这些统计信息进行解决。
(3)如果数据持续增长,达到现有分表的瓶颈,需要增加分表,此时会出现数据重新排列的情况;这就需要分析数据增长速度,提前计算出未来某段时间需要用到分表的个数。

水平分表在插入数据时,只需要计算出该条数据所在那张数据表,比如根据id%3计算,得到1,则在业务中将table拼接成user1即可。

二、分库

分库可以解决单台数据库的并发访问压力问题,每个物理数据库支持数据都是有限的,每一次的数据库请求都会产生一次数据库链接,当一个库无法支持更多访问的时候,我们会把原来的单个数据库分成多个,帮助分担压力。

1、纵向分库

纵向分库就是根据业务耦合性,将关联度低的不同表存储在不同的数据库,做法与大系统拆分为多个小系统类似,按业务分类进行独立划分。
数据库分表分库及分表分库带来的问题

2、横向分库

当一个应用难以再细粒度的纵向分库,或切分后数据量行数巨大,存在单库读写、存储性能瓶颈,这时候就需要进行横向分库了。
相对于水平分表,显然横向分库后,表格也被水平切分,但是每个表放在不同的数据库。
数据库分表分库及分表分库带来的问题

三、分库分表带来的问题及解决

1、事务一致性问题

(1)分布式事务
当更新内容同时分布在不同库中,不可避免会带来跨库事务问题。没有简单的方案,一般可使用“XA协议”和“两阶段提交”处理。
分布式事务能够最大限度地保证数据库操作地原子性,但是提交事务时需要协调多个节点,推后了提交事务的时间点,延长了事务的执行时间。导致事务在访问共享资源时发生冲突或死锁的概率增高。
(2)最终一致性
对于性能要求很高,但对一致性要求不高的系统,只要在允许的时间段内达到最终一致性即可,可采用事务补偿的方式。与事务在执行中发生错误后立即回滚的方式不同,事务补偿是一种事后检查补救的措施,一些常见的实现方法有:对数据进行对账检查,基于日志进行对比,定期同标准数据来源进行同步等等。事务补偿还要结合业务系统来考虑。

2、跨节点关联查询问题

单表单库时,不同分库分表之间的关联查询通过JOIN操作就可以简单解决。但是切分之后在使用JOIN带来的问题就很麻烦。解决此问题的方法有以下几种:
(1)全局表
就是将业务的基础数据或者说是所有模块都可能依赖的一些表,在每个数据库都保存一份,这些数据很少会修改,因此也不需要担心一致性的问题。
(2)字段冗余
这是一种反范式设计,利用空间换时间,比如订单表在存储user_id的时候将user_name也冗余保存一遍,这样查询订单详情时,就不需要再去查询用户user表了。
这种方法比较适用于依赖字段比较少的情况,而且冗余字段的数据一致性比较难保证,比如user表的user_name修改后,是否需要在历史订单里更新用户的user_name。
(3)数据组装
分两次查询,第一次先查出关联数据id,第二次根据id查询出关联数据,然后将获得的数据进行字段拼装。
(4)ER分片
关系型数据库中,如果能够确定好表与表之间的关系,可以将有关联关系的数据放在同一个分片上。

3、跨节点分页、排序、函数问题

在单库单表的情况下,分页和排序也是非常容易的。但是,随着分库与分表的演变,也会遇到跨库排序和跨表排序问题。为了最终结果的准确性,需要在不同的分库分表中将数据进行排序并返回,并将不同分库分表返回的结果集进行汇总和再次排序,最后再返回给用户。
对于MAX、MIN、SUM、COUNT等函数操作,同样需要对不同分库分表进行计算后,在汇总进行计算,最后返回。

4、全局主键避重问题

分库分表中,主键自增长无法适用,不同分库分表中也无法保证自生成的ID全局唯一。
(1)UUID
UUID指在一台机器上生成的数字,它保证对在同一时空中的所有机器都是唯一的,其标准形式包含32个16进制数。
该方法最简单,本地生成,性能高,没有网络耗时;但是缺点也很明显,UUID很长,占空间大,另外其无序性会引起数据频繁变动,导致分页。
(2)结合数据库维护主键ID表

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

stub设置为唯一索引,则同一stub值在sequence表中只能有一条记录,可以同时为多张表生成全局ID。
当需要全局唯一ID时,

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

这两条语句是Connection级别的,select last_insert_id()必须与replace into在同一数据库连接下才能得到刚刚插入的新ID。
该方法加强版可参考flickr团队使用的一种主键生成策略
(3)Snowflake分布式自增ID算法
生成64位的Long型数字,组成部分:
第一位未使用
接下来41位是毫秒级时间,41位的长度可以表示69年的时间
5位datacenterId,5位workerId。10位的长度最多支持部署1024个节点
最后12位是毫秒内的计数,12位的计数顺序号支持每个节点每毫秒产生4096个ID序列
好处:毫秒数在高位,生成ID整体上按时间趋势递增,不依赖第三方系统,稳定性和效率较高。不足在于强烈依赖机器时钟,如果时钟回拨,可能导致ID重复。
(4)Leaf——美团点评分布式ID生成系统
Leaf链接
业界较为成熟解法,考虑到了高可用、容灾、分布式下时钟等问题。