分库分表详述

分库分表的方法

  1. 垂直拆分
  2. 水平拆分

垂直拆分

垂直拆分,顾名思义就是对数据库竖着拆分,也就是将数据库的表拆分到多个不同的数据库中。

垂直拆分的原则一般是按照业务类型来拆分,核心思想是专库专用,将业务耦合度比较高的表拆分到单独的库中。举个形象的例子,就是在整理衣服的时候,将羽绒服、毛衣、T恤分别放在不同的格子里。这样可以解决我在开篇提到的第三个问题:把不同的业务的数据分拆到不同的数据库节点上,这样一旦数据库发生故障时只会影响到某一个模块的功能,不会影响到整体功能,从而实现了数据层面的故障隔离。

我还是以微博系统为例来给你说明一下。

在微博系统中有和用户相关的表,有和内容相关的表,有和关系相关的表,这些表都存储在主库中。在拆分后,我们期望用户相关的表分拆到用户库中,内容相关的表分拆到内容库中,关系相关的表分拆到关系库中。

在这里插入图片描述
对数据库进行垂直拆分是一种偏常规的方式,这种方式其实你会比较常用,不过拆分之后,虽然可以暂时缓解存储容量的瓶颈,但并不是万事大吉,因为数据库垂直拆分后依然不能解决某一个业务模块的数据大量膨胀的问题,一旦你的系统遭遇某一个业务库的数据量暴增,在这个情况下,你还需要继续寻找可以弥补的方式。

比如微博关系量早已经过了千亿,单一的数据库或者数据表已经远远不能满足存储和查询的需求了,这个时候,你需要将数据拆分到多个数据库和数据表中,也就是对数据库和数据表做水平拆分了。

水平拆分

和垂直拆分的关注点不同,垂直拆分的关注点在于业务相关性,而水平拆分指的是将单一数据表按照某一种规则拆分到多个数据库和多个数据表中,关注点在数据的特点。

拆分的规则有下面这两种:

  1. 按照某一个字段的哈希值做拆分,这种拆分规则比较适用于实体表,比如说用户表,内容表,我们一般按照这些实体表的ID字段来拆分。比如说我们想把用户表拆分成16个库,每个库是64张表,那么可以先对用户ID做哈希,哈希的目的是将ID尽量打散,然后再对16取余,这样就得到了分库后的索引值;对64取余,就得到了分表后的索引值。
    在这里插入图片描述
  2. 另一种比较常用的是按照某一个字段的区间来拆分,比较常用的是时间字段。你知道在内容表里面有“创建时间”的字段,而我们也是按照时间来查看一个人发布的内容。我们可能会要看昨天的内容,也可能会看一个月前发布的内容,这时就可以按照创建时间的区间来分库分表,比如说可以把一个月的数据放入一张表中,这样在查询时就可以根据创建时间先定位数据存储在哪个表里面,再按照查询条件来查询。

一般来说,列表数据可以使用这种拆分方式,比如一个人一段时间的订单,一段时间发布的内容。但是这种方式可能会存在明显的热点,这很好理解嘛,你当然会更关注最近我买了什么,发了什么,所以查询的QPS也会更多一些,对性能有一定的影响。另外,使用这种拆分规则后,数据表要提前建立好,否则如果时间到了2020年元旦,DBA(Database Administrator,数据库管理员)却忘记了建表,那么2020年的数据就没有库表可写了,就会发生故障了

在这里插入图片描述
数据库在分库分表之后,数据的访问方式也有了极大的改变,原先只需要根据查询条件到从库中查询数据即可,现在则需要先确认数据在哪一个库表中,再到那个库表中查询数据。

不过,你要知道的是,分库分表虽然能够解决数据库扩展性的问题,但是它也给我们的使用带来了一些问题。

分库分表带来的问题

分区键

分库分表引入的一个最大的问题就是引入了分库分表键,也叫做分区键,也就是我们对数据库做分库分表所依据的字段。

从分库分表规则中你可以看到,无论是哈希拆分还是区间段的拆分,我们首先都需要选取一个数据库字段,这带来一个问题是:我们之后所有的查询都需要带上这个字段,才能找到数据所在的库和表,否则就只能向所有的数据库和数据表发送查询命令。如果像上面说的要拆分成16个库和64张表,那么一次数据的查询会变成16*64=1024次查询,查询的性能肯定是极差的。

当然,方法总比问题多,针对这个问题,我们也会有一些相应的解决思路。

  1. 将常用查询条件都设为分区键
    比如,在用户库中我们使用ID作为分区键,这时如果需要按照昵称来查询用户时,你可以按照昵称作为分区键再做一次拆分
  2. 建立分区键和查询字段的映射
    第一种方式会极大地增加存储成本,如果以后我们还需要按照注册时间来查询时要怎么办呢,再做一次拆分吗?所以最合适的思路是你要建立一个昵称和ID的映射表,在查询的时候要先通过昵称查询到ID,再通过ID查询完整的数据,这个表也可以是分库分表的,也需要占用一定的存储空间,但是因为表中只有两个字段,所以相比重新做一次拆分还是会节省不少的空间的。

不同实例间join实现复杂

分库分表引入的另外一个问题是一些数据库的特性在实现时可能变得很困难。比如说多表的JOIN在单库时是可以通过一个SQL语句完成的,但是拆分到多个数据库之后就无法跨库执行SQL了,不过好在我们对于JOIN的需求不高,即使有也一般是把两个表的数据取出后在业务代码里面做筛选,复杂是有一些,不过是可以实现的。再比如说在未分库分表之前查询数据总数时只需要在SQL中执行count()即可,现在数据被分散到多个库表中,我们可能要考虑其他的方案,比方说将计数的数据单独存储在一张表中或者记录在Redis里面。

总结

总的来说,在面对数据库容量瓶颈和写并发量大的问题时,你可以采用垂直拆分和水平拆分来解决,不过你要注意,这两种方式虽然能够解决问题,但是也会引入诸如查询数据必须带上分区键,列表总数需要单独冗余存储等问题。

而且,你需要了解的是在实现分库分表过程中,数据从单库单表迁移多库多表是一件既繁杂又容易出错的事情,而且如果我们初期没有规划得当,后面要继续增加数据库数或者表数时,我们还要经历这个迁移的过程。所以,从我的经验出发,对于分库分表的原则主要有以下几点:

  1. 如果在性能上没有瓶颈点那么就尽量不做分库分表;

  2. 如果要做,就尽量一次到位,比如说16库,每个库64表就基本能够满足几年内你的业务的需求。

  3. 很多的NoSQL数据库,例如Hbase,MongoDB都提供auto sharding的特性,如果你的团队内部对于这些组件比较熟悉,有较强的运维能力,那么也可以考虑使用这些NoSQL数据库替代传统的关系型数据库。

其实,在我看来,有很多人并没有真正从根本上搞懂为什么要拆分,拆分后会带来哪些问题,只是一味地学习大厂现有的拆分方法,从而导致问题频出。所以,你在使用一个方案解决一个问题的时候一定要弄清楚原理,搞清楚这个方案会带来什么问题,要如何来解决,要知其然也知其所以然,这样才能在解决问题的同时避免踩坑。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值