转载~
在很多小型应用中都没真正使用分库分表,但是说起来并不陌生,因为我们在面试中经常会被问到,今天我们从从以下几个方面来聊聊分库分表:「是什么?解决什么?怎么做?为什么要这么做?即:」
分库分表其实很好理解,「顾名思义,即把存于一个库的数据分散到多个库中,把存于一个表的数据分散到多个表中」。但是需要明确一点,分库分表不是一件事,而是三件事,也就是「分库分表的三种方案」:
「从单个数据库拆分成多个数据库的过程,将数据散落在多个数据库中,多个数据库同时提供服务」。
「从单张表拆分成多张表的过程,将数据散落在多张表内」。
「把存于一个数据库的单表数据分散到不同库的多个表中」。
大型应用系统需要处理大量用户的请求,比如微信,美团,淘宝等每天都会产生海量的数据,我们知道当数据量或者请求数达到一定数量之后,数据库就会产生性能瓶颈,而为了缓解数据量的压力,比较普遍的方案一方面是使用NoSQL,而另一方面就是分库分表。
分库就是在我们系统的业务量增长到一定程度之后,「解决数据库本身的性能瓶颈问题」,所以「一般都是业务量增长,遇到以下两种情况时就会考虑分库」:
在之前的《单表最大2000W行数据》中,分析了单表的数据存储量,当单表存储的数据过多时,查询数据会非常慢,在高并发场景下,一个慢查询可能会导致整个数据库的宕机。所以「一般当单个表的数据量太大,可以考虑通过分表来提升查询效率」。
一般推荐单表数据量在 上千万级别时就要考虑分表,因为千万级别可能会导致B+Tree高度变高。 不清楚单表数据量存储量大小的,可以参考一下单表最大2000W行数据这篇文章。
「因此分表可以解决单个表数据被分散,查询是B+Tree(MySQL)的高度比较低,减少磁盘IO,提升效率」。
「当我们使用分库分表时,都在物理空间的拆分,主要有两种拆分模式,都可以应用到分库或分表中」:
随着业务快速发展,数据库中的数据量猛增,所有的数据限制在一台服务器,「数据库物理机本身的CPU、内存、网络IO、磁盘等都会成为性能瓶颈」,此时我们可以按照业务的划分,将不同的表放在不同的服务器中,分散流量,减轻单个数据库的压力,提高系统的性能。
「垂直分库本质是专库专用,指按照业务将表进行分类,分布到不同的数据库中,每个库可以放在不同的服务器上」。
在之前的文章《InnoDB的存储结构》中解释了了数据在MySQL的的存储方式,我们知道数据是以数据页的方式存储的,而数据页中的数据是数据行,因此「当我们的一行数据过大时,数据页存储的数据行就会减少,也就是说跨数据页查询的概率就会增加」,因此垂直分表就是将一个表拆分到多个表,避免出现数据库跨页存储的问题,从而提升查效率。
「垂直分表本质是将一个表按照字段分成多表,每个表存储其中一部分字段」。
垂直分库是将不同业务表分别放在了不同数据库中以此减轻单个数据库的性能瓶颈,但是「如果某个核心业务的并发非常高,比如订单库,双十一下单的并发非常高,单个的订单库仍然存在单个订单数据库性能瓶颈问题,因此我们可以对数据进行分片,将单个订单库进行拆分成多个库,以此提高数据库总体性能」。
「水平分库的本质也是分表,是把同一个表的数据按一定规则拆到不同的数据库中,每个库可以放在不同的服务器上」。
当我们的业务量猛增,「单表数据达数千万甚至上亿的时候,查询效率会降低,此时我们考虑将数据表按照一定的规则将表中的记录进行分片,存储在不同的表中,以此提高查询效率」。
具体单表的存储数据量在《单表最大2000W行数据》文中有详细介绍,有兴趣的可以看看,
「水平分表的本质是数据分片,将不同的数据按照一定的规则( hash取模/range范围)将数据存储在不同的表中,以此减少单表的数据量,提高查询效率」。
通过以上分别对【 垂直分表、 垂直分库,水平分表、水平分库】的分析,可以看出来无论是分库还是分表,垂直划分和水平划分的时候,他们的优缺点很类似,所以接下来总结一下分库分表解决了什么问题/引发什么问题。
分库分表之后,虽然能够解决数据库的性能问题,但是也带来了一系列的其他问题:
分库分表之后一个无法避免的问题就是问题问题,这也是一个非常频繁的面试问题:分布式事务,针对此类问题常用解决方案有:本地事务表,基于可靠消息(MQ)的解决方案、两阶段事务提交等。具体的解决放方案可参考我之前的文章《分布式集群:分布式事物解决⽅案》,这里不再展开。
在单库单表中,我们经常使用JOIN来进行多表查询,但是经过分库分表后多个表可能存在于多个数据库中,无法直接使用join进行联表查询,但是联表查询是非常常见的,所以针对这种情况有以下几种解决方式
分库分表之后,数据分散,在跨节点进行count,order by,group by,limit 以及聚合函数的时候需要特殊处理,可以「采用分片的方式:先在每个分片上执行相应的函数,得到结果后在应用程序端进行合并,得到最终结果」。
分库分表之后,我们不能再依赖数据库自增主键了,分表以后每个表都可以自增,会导致ID 重复或者混乱的问题,因此我们需要单独设计全局主键,以避免跨库主键重复问题。有一些常见的主键生成策略:
针对分布式ID的问题,有兴趣的可以看看我之前的文章《分布式集群:分布式ID解决⽅案》,文中详细介绍了几种分布式ID的实现方式,这里就不再展开赘述。
多数据源主要针对分库,既然数据库变成了多个,那什么时候查询那个库必然是一个必须要解决的问题,一般的解决方式有:「应用程序适配和代理层适配」。一般我们都会使用比较成熟的中间件来处理。
目前市面上有很多比较成熟的分库分表中间件,可以帮助我们解决分库分表后多数据源问题
这里简单介绍一下shardingsphere和Mycat,也是用的比较多的
「MyCat属于服务器数据库中间件,是一个基于第三方应用中间件数据库代理框架,客户端所有的jdbc请求都必须要先交给MyCat,再有MyCat转发具体的真实数据库服务器中」。
「ShardingJdbc是一个本地数据库中间件框架,以一个Jar形式在本地应用层重写jdbc原生的方法,实现数据库分片形式」。