分库分表问题

1,606 阅读6分钟

1. 分表类型

(1) 水平分表

1) 什么是水平分表

水平分表就是指以行为单位对数据进行拆分,一般意义上讨论的分表指的就是水平分表。
分表之后,所有表的结构都是一样的。

2) 为什么要水平分表

水平分表可以解决一个表数据量太大的问题,如果一张表的数据太多,会对性能造成影响。

3) 如何进行水平分表

一般可以有范围(range)法和hash法来进行水平分表。

假设现在有3万行数据,需要对它们进行水平分表:

范围法很好理解,可以让第1 ~ 10000行数据存放在表1,第10001 ~ 20000行数据存放在表2,第20001 ~ 30000行数据存放在表3,就完成了水平分表。
这种方案的优点是利于扩容,不需要做数据迁移,直接增加新表即可。但是每次新增数据时,都只是在最后一个表中进行插入,这就导致了单表热点问题。

hash法也不难理解,就是选择一个合适的hash函数,比如说使用取模操作(%),把%3结果为0的数据存放在表1,结果为1的存放在表2,结果为2的存放在表3即可。
这种方案的优点就是能解决单表热点问题,但是不利于扩容,因为一旦扩容后hash的结果就会改变,只能进行数据迁移重新进行hash。

(2) 垂直分表

1) 什么是垂直分表

垂直分表就是把一张表按列分为多张表,多张表通过主键进行关联,从而组成完整的数据。
分表之后,每张表的结构都不相同。

2) 为什么要垂直分表

以Mysql的InnoDB引擎来说:

在Mysql中,数据的基本单位,称为 (Row)
管理数据的基本单位,称为 (Page)
保存页的单位,称为 (Extent)
一个区由连续的页组成,一个页由连续的行组成

默认每一个区的大小为1M,默认每一页的大小为16K,所以一个区有1024 / 16 = 64个页
而每个页中,根据每行数据的大小,又可以存储多个行

然而,在InnoDB 1.0版本之后,引入了一个新特性:压缩页
被压缩之后的页在实际占用空间上,比逻辑上的要小,但是在压缩和解压缩时会花费额外的时间
这时如果出现了跨页检索数据,效率就会比较低

因此,在表设计时,尽可能的在页内多存储数据行,这样可以减少跨页检索,提高效率。
而多存储数据行就意味着,每一行的列数要尽可能少。

3) 如何进行垂直分表

既然要求每一行的列数尽可能少,那直接每个表就只存储一两列行不行?

当然不行!
这样做不仅会造成表过多,而且会产生大量重复字段(比如主键),在查询时还需要不断地进行多表关联
所以,如何进行垂直分表还是有技巧的。

一般来说,垂直分表并不会把列平分到2个表中,而是会将一些重要的字段单独剥离成小表,把剩余的不太重要的字段放在大表中
比如,把查询、排序时需要的字段,高频访问的小字段放在小表
而把低频访问字段以及一些大字段放在大表中。

2. 有没有既能解决热点问题,又方便扩容的分表方案呢?

上边我们说了,range法方便扩容,不需要数据迁移,但是存在热点问题;
hash法不存在热点问题,但是扩容时需要进行数据迁移。
如何把两者的优点结合起来呢?

假设我们现在有3个数据库(DB1、DB2和DB3),每个库中有3个表(Table_0 ~ Table_2),它们组成了一个group,共同来存储3000万条数据。在每个库中,都通过range法来对表的存储范围进行划分。比如Table_0用来存储id为0 ~ 1000万的数据,Table_1用来存储id为1000万 ~ 2000万的数据,Table_2用来存储id为2000万 ~ 3000万的数据。

那在插入新数据时,应该插入到哪个库的哪个表中呢,可以通过hash来决定,这里的模数就是表的总数 9,结果为0、1、2就插入DB1,结果为3、4、5就插入DB2,结果为6、7、8就插入DB3,再根据range的结果来真正的插入对应的表中。

举个例子,现在我们要插入id为1500万的数据,因为1500万 % 9 = 6,所以需要插入到DB3中,根据range的划分,最终插入到DB3中的Table_1表中了。

如果现在需要再扩容3000万条数据怎么办?那就再增加一个group就行了。

所以最终的流程应该是这样:先根据id决定属于哪个group,再根据hash的结果决定数据库,最终根据range的结果决定表。

(可以参考这篇文章,讲的很详细,而且有图解。)

3. 如果每个数据库的性能不一样怎么办,如何优化数据分布?

比如DB1的性能最好,DB2次之,DB3的性能最差,如何根据它们的性能优化数据分布呢?

其实很简单,微调上边说的方案,性能高的库多加表,性能低的库少加表就行了

比如,DB1中创建5张表,DB2中创建3张表,DB3中只创建1张表。

为什么这样就可以了?别忘了我们在做hash时,是根据表的总数进行取模的,所以一个库中表的个数越多,分配到这个库上的概率就越大,反之就越小。这样就能根据数据库的性能来决定数据的分布情况了。

4. 如何把单库单表的数据迁移到分库分表上?

如果我有一个拥有大量数据的单库单表,现在想要把数据迁移到分库分表上,有哪些方案?

(1) 停机迁移方案

第一种方案就是停机,然后再进行数据的迁移。这种方案是肯定不会选择的。

(2) 双写迁移方案

双写就是说,在对老库进行修改的同时,也对新库进行同样的修改,然后再进行数据的迁移。迁移完成后,需要将老库和新库中的数据进行比较,更新不匹配的数据到新库中,最后再将配置从老库切换到新库上。
比如可以使用Canal做增量数据同步,使用Sharding-Proxy来做数据迁移。

更多的内容可以参考以下文章:
Sharding-Jdbc实现读写分离 + 分库分表
ShardingSphere实现数据迁移