数据库分库分表


一、背景

  • 大量的并发读/写操作,导致单库出现难以承受的负载压力
  • 单表存储数据量过大,导致索引效率低下

二、读写分离

将数据库设置为读写分离状态,由Master(主)负责写数据,Salve(从)只负责读数据。主从之间保持数据同步。根据二八法则,80%的数据库操作是读,20%则为写。读写分离后,可以大大提升单库负载压力。

但是,当Master存在TPS(系统吞吐量)较高的情况,Master和Slave数据库之间数据同步是会存在一定延迟,因此在写入Master之前最好还是要将同一份数据放入缓存中,以避免高并发情况下,从Slave中获取不到指定数据的情况

三、分库分表

随着用户规模的不断上升,仅仅只是依靠数据库的读写分离并不能解决系统瓶颈。因此需要考虑分库分表的解决方案。

分表 就是将原来冗余在单库中的单个业务表拆分为N个“逻辑相关”的业务子表(如tab_0000, tab_0001, tab_0002…),不同的业务字表各自负责存储不同区间数据,对外形成一个整体

分库 就是将分表后的业务子表按照特定的算法和规则分散到N个“逻辑相关”的业务子库中(如db_0000, db_0001, db_0002…)

分库分表策略

主要原理:分区,取模,数据路由表

1、按时间区间

一定时间区间内产生的数据放到一张表里面,多个时间区间的表放到一个库里面

比如,

a、单库多表结构,按月分表可以这样:user_201701, user_201702…user_201712;按年分表可以这样,user_2016, user_2017…

b、多库多表,比如按天分表,每天一张表,当单库超过100张表的时候,进行分库到下一张表。那么假如第一张表在库db0,表名是user_20160201。从db0.user_20160201 - db0.user_20160511就是100张表了,接下来就是分库,进入20160512,就是db1.user_20160512,这个算法就是上线的时候定一个上线日期,具体算法如下:

1
2
库ID = (当前日期 - 上线日期)/ 100
表ID = user_yyyyMMdd

注:好处是可以直接根据时间经过简单计算定位到哪个库和哪个表

还有一种算法:

1
2
表ID = (当前日期 - 上线日期) % 100
表名如下: DB0.user_0001,user_0002 .... user_01000

注:表名和库名都要经过计算,比较麻烦

c、按月分表,每个月一张表。这种情况,一般就不用分库了,一年12张表说明量也不会特别大,如果量特别大,或者是热点数据,可以一年分一个库,具体算法和上面差不多。

d、按季度分表,按年度分表(基本不用分库)

2、按主键ID区间

对于自增的主键ID,可以按照ID区间进行分表,以1000万数据量为分界线,对线性的ID进行切割分表,每涨到1000万数据,分到下一张表,超过一定数目的表,进行分库。

1
2
库ID = 主键ID / 1000万 / 100
表ID = 主键ID / 1000万 % 100

如:

1
2
db0.user_0000 ... db0.user_0099
db1.user_0000 ... db1.user_0099

3、按照用户ID取模

这里把按照用户ID取模单独拎出来,因为就使用而言,是使用场景最多的情况,很多时候都是用户相关数据量最大,需要分库分表,查询维度更多也是按照用户来查询,所以对用户取模,让同一个用户的数据落到一张表里面,再好不过了。

这里模式用户ID是整数型的。假设库数量要分4库,每个库表数量8表,一共32张表。

1
2
3
4
5
库ID = userId % 库数量4
表ID = userId / 库数量4 % 表数量8
或者
库ID = userId / 表数量4 % 库数量4
表ID = userId % 表数量8

4、按照指定字段hash后再取模

如果要取模的字段不是整数型,要先hash后,再通过取模算法,算出在哪个库和那个表。具体算法,参照上面的按用户ID取模。

5、数据路由表

如果分库分表的算法很复杂,可以通过路由表+程序算法,来存储和计算分库分表规则,不过一般不建议,分库分表搞得太复杂,不便于维护和查询问题

四、第三方中间件

1、Cobar

开源地址:https://github.com/alibaba/cobar

2、Shark

开源地址:https://github.com/gaoxianglong/shark

五、需要考虑的问题

一旦数据库实施分库分表后,便会对开发造成一定的影响。具体问题如下:

  • 数据的原子性,一致性,隔离性,持久性如何保证
  • 多表之间的关联查询如何进行
  • 无法继续使用外键约束
  • 无法继续使用Oracle提供的Sequence或MySQL提供的AUTO_INCREMENT生成全局唯一和连续性ID。

参考文献