目录
相关内容:数据库分区: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链接
业界较为成熟解法,考虑到了高可用、容灾、分布式下时钟等问题。