此文参阅其他作者写的文章进行了汇总,参阅文献在文末。

前语

首要,能不分库分表就不分库分表。分库分表会带来许多问题,比如分布式事务、全局的仅有性id、效果集的兼并等等。

那什么是分库分表呢?

其实分库分表根柢不是一件事,而是三件事。

那到底是哪三件事呢?

这三个事儿分别是”只分库不分表”、”只分表不分库”、以及”既分库又分表”。

什么是分库

分库首要处理的是并发量大的问题。因为并发量一旦上来了,那么数据库就或许会成为瓶颈,因为数据库的连接数是有限的,虽然可以调整,但是也不是无限调整的。

所以,当当你的数据库的读或许写的QPS过高,导致你的数据库连接数不足了的时分,就需求考虑分库了,通过增加数据库实例的办法来供应更多的可用数据库链接,从而进步系统的并发度。

比较典型的分库的场景就是我们在做微服务拆分的时分,就会按照事务距离,把各个事务的数据从一个单一的数据库中拆分隔,分表把订单、物流、商品、会员等单独放到单独的数据库中。

一阶段:单运用单数据库

在前期创业阶段想做一个商城系统,根柢就是一个系统包括多个基础功用模块,终究打包成一个 war 包布置,这就是典型的单体架构运用。

总算有人能把分库分表讲理解了

商城项目运用单数据库

如上图,商城系统包括主页 Portal 模板、用户模块、订单模块、库存模块等,全部的模块都共有一个数据库,一般数据库中有非常多的表。

因为用户量不大,这样的架构在前期完全适用,开发者可以拿着 demo 到处找(骗)投资人。

一旦拿到投资人的钱,事务就要初步大规模推行,一起系统架构也要匹配事务的快速发展。

二阶段:多运用单数据库

在前期为了抢占市场,这一套系统不停地迭代更新,代码量越来越大,架构也变得越来越臃肿,现在跟着系统访问压力逐渐增加,系统拆分就势在必行了。

为了确保事务平滑,系统架构重构也是分了几个阶段进行。

第一个阶段将商城系统单体架构按照功用模块拆分为子服务,比如:Portal 服务、用户服务、订单服务、库存服务等。

总算有人能把分库分表讲理解了

如上图,多个服务同享一个数据库,这样做的意图是底层数据库访问逻辑可以不用动,将影响降到最低。

三阶段:多运用多数据库

跟着事务推行力度加大,数据库总算成为了瓶颈,这个时分多个服务同享一个数据库根柢不行行了。我们需求将每个服务相关的表拆出来单独建立一个数据库,这其实就是“分库”了。

单数据库可以支撑的并发量是有限的,拆成多个库可以使服务间不用比赛,进步服务的功用。

总算有人能把分库分表讲理解了

如上图,从一个大的数据中分出多个小的数据库,每个服务都对应一个数据库,这就是系统发展到必定阶段有必要要做的“分库”操作。

现在非常火的微服务架构也是相同的,假设只拆分运用不拆分数据库,不能处理根柢问题,整个系统也很简略到达瓶颈。

什么是分表

分库首要处理的是并发量大的问题,那分表其实首要处理的是数据量大的问题。

假设你的单表数据量非常大,因为并发不高,数据量连接或许还够,但是存储和查询的功用遇到了瓶颈了,你做了许多优化之后仍是无法进步功率的时分,就需求考虑做分表了。

总算有人能把分库分表讲理解了

通过将数据拆分到多张表中,来减少单表的数据量,从而进步查询速度。

一般我们认为,单表行数逾越 500 万行或许单表容量逾越 2GB之后,才需求考虑做分库分表了,小于这个数据量,遇到功用问题先主张我们通过其他优化来处理。

什么的是既分库又分表

那么什么时分分库又分表呢,那就是既需求处理并发量大的问题,又需求处理数据量大的问题时分。一般情况下,高并发和数据量大的问题都是一起产生的,所以,我们会常常遇到分库分表需求一起进行的情况。

所以,当你的数据库链接也不够了,而且单表数据量也很大导致查询比较慢的时分,就需求做既分库又分表了。

什么情况需求考虑分库分表呢

分库分表的根柢原因是:数据库出现功用瓶颈。用大白话来说就是数据库快扛不住了。

数据库出现功用瓶颈,对外表现有几个方面:

  • 许多央求堵塞

    在高并发场景下,许多央求都需求操作数据库,导致连接数不够了,央求处于堵塞情况。

  • SQL 操作变慢

    假设数据库中存在一张上亿数据量的表,一条 SQL 没有射中索引会全表扫描,这个查询耗时会非常久。

  • 存储出现问题

    事务量剧增,单库数据量越来越大,给存储造成巨大压力。

从机器的角度看,功用瓶颈无非就是 CPU、内存、磁盘、网络这些,要处理功用瓶颈最简略粗暴的办法就是进步机器功用,但是通过这种办法成本和收益投入比往往又太高了,不划算,所以关键仍是要从软件角度下手。

多少的数据量需求考虑分库分表呢?

在我国互联网技术圈有一句广为流传的说法:MySQL 单表数据量大于 2000 万行,功用会显着下降。

事实上,这个传闻听说最早起源于百度。

详细情况大概是这样的,当年的 DBA 测试 MySQL功用时发现,当单表的量在 2000 万行量级的时分,SQL 操作的功用急剧下降,因此,结论由此而来。

然后又听说百度的工程师流动到业界的其它公司,也带去了这个信息,所以,就在业界流传开这么一个说法。

再后来,阿里巴巴《Java 开发手册》提出单表行数逾越 500 万行或许单表容量逾越 2GB,才推荐进行分库分表。对此,有阿里的黄金铁律支撑,所以,许多人规划大数据存储时,多会以此为规范,进行分表操作。

我对于分库分表的观点是,需求结合实践需求,不宜过度规划,在项目一初步不选用分库与分表规划,而是跟着事务的增加,在无法持续优化的情况下,再考虑分库与分表进步系统的功用。

对此,阿里巴巴《Java 开发手册》补充到:假设估计三年后的数据量根柢达不到这个等级,请不要在创立表时就分库分表。

垂直拆分和水平拆分

就拿用户表(user)来说,表中有 7 个字段:id,name,age,sex,nickname,description,假设 nickname 和 description 不常用,我们可以将其拆分为其他一张表:用户详细信息表,这样就由一张用户表拆分为了用户根柢信息表+用户详细信息表,两张表结构不相同彼此独立。但是从这个角度来看垂直拆分并没有从根柢上处理单表数据量过大的问题,因此我们仍是需求做一次水平拆分。

总算有人能把分库分表讲理解了

可以按照 id 拆表,还可以按照时间维度去拆分,比如订单表,可以按每日、每月等进行拆分。

  • 每日表:只存储当天的数据。
  • 每月表:可以起一个守时任务将前一天的数据悉数搬家到当月表。
  • 前史表:相同可以用守时任务把时间逾越 30 天的数据搬家到 history 表。

总结一下水平拆分和垂直拆分的特色:

  • 垂直拆分:依据表或字段差异,表结构不同。
  • 水平拆分:依据数据差异,表结构相同,数据不同。

分表字段的选择

在分库分表的过程中,我们需求有一个字段用来进行分表,比如按照用户分表、按照时间分表、按照区域别表。这里边的用户、时间、区域就是所谓的分表字段。

那么,在选择这个分表字段的时分,必定要注意,要依据实践的事务情况来做稳重的选择。

比如说我们要对买卖订单进行分表的时分,我们可以选择的信息有许多,比如买家Id、卖家Id、订单号、时间、区域等等,详细应该怎样选择呢?

一般,假设有特其他诉求,比如按照月度汇总、区域汇总等以外,我们一般主张我们按照买家Id进行分表。因为这样可以防止一个要害的问题那就是——数据倾斜(热门数据)。

买家仍是卖家?

首要,我们先说为什么不按照卖家分表?

因为我们知道,电商网站上面是有许多买家和卖家的,但是,一个大的卖家或许会产生许多订单,比如像苏宁易购、当当等这种店肆,他每天在天猫产生的订单量就非常的大。假设按照卖家Id分表的话,那同一个卖家的许多订单都会分到同一张表。

那就会使得有一些表的数据量非常的大,但是有些表的数据量又很小,这就是产生了数据倾斜。这个卖家的数据就变成了热门数据,跟着时间的增加,就会使得这个卖家的全部操作都变得失常缓慢。

总算有人能把分库分表讲理解了

但是,买家ID做分表字段就不会出现这类问题,因为一个不太简略出现一个买家能把数据买倾斜了。

但是需求注意的是,我们说按照买家Id做分表,确保的是同一个买家的全部订单都在同一张表 ,并不是要给每个买家都单独分配一张表。

我们在做分表路由的时分,是可以设定必定的规矩的,比如我们想要分1024张表,那么我们可以用买家ID或许买家ID的hashcode对1024取模,效果是0000-1023,那么就存储到对应的编号的分表中就行了。

卖家查询怎样办?

假设按照买家Id进行了分表,那卖家的查询怎样办,这不就意味着要跨表查询了吗?

首要,事务问题我们要建立在事务布景下谈论。电商网站订单查询有几种场景?

  • 买家查自己的订单
  • 卖家查自己的订单
  • 平台的小二查用户的订单。

首要,我们用买家ID做了分表,那么买家来查询的时分,是必定可以把买家ID带过来的,我们直接去对应的表里边查询就行了。

那假设是卖家查呢?卖家查询的话,相同可以带卖家id过来,那么,我们可以有一个依据binlog、flink等准实时的同步一张卖家维度的分表,这张表只用来查询,来处理卖家查询的问题。

总算有人能把分库分表讲理解了

本质上就是用空间换时间的做法。

不知道我们看到这里会不会有这样的疑问:同步一张卖家表,这不又带来了大卖家的热门问题了吗?

首要,我们说同步一张卖家维度的表来,但是其实全部的写操作仍是要写到买家表的,只不过需求准实时同步的计划同步到卖家表中。也就是说,我们的这个卖家表理论上是没有事务的写操作,只需读操作的。

所以,这个卖家库只需求有高功用的读就行了,那这样的话就可以有许多选择了,比如可以布置到一些配备不用那么高的机器、或许其实可以爽性就不用MYSQL,而是选用HBASE、PolarDB、Lindorm等数据库就可以了。这些数据库都是可以海量数据,并供应高功用查询的。

还有呢就是,大卖家一般都是可以识其他,提前针对大卖家,把他的订单,再按照必定的规矩拆分到多张表中。因为只需读,没有写操作,所以拆分多张表也不用考虑事务的问题。

按照订单查询怎样办?

上面说的都是有买卖家ID的情况,那没有买卖家ID呢?用订单号直接查怎样办呢?

这种问题的处理计划是,在生成订单号的时分,我们一般会把分表处理编码到订单号中去,因为订单生成的时分是必定可以知道买家ID的,那么我们就把买家ID的路由效果比如1023,作为一段固定的值放到订单号中就行了。这就是所谓的”基因法”

总算有人能把分库分表讲理解了

这样按照订单号查询的时分,解分出这段数字,直接去对应分表查询就好了。

至于还有人问其他的查询,没有买卖家ID,也没订单号的,那其实就属所以低频查询或许非核心功用查询了,那就可以用ES等搜索引擎的计划来处理了。就不赘述了。

分表算法

选定了分表字段之后,怎样依据这个分表字段来准确的把数据分表到某一张表中呢?

这就是分表算法要做的事情了,但是不管什么算法,我们都需求确保一个前提,那就是同一个分表字段,通过这个算法处理后,得到的效果必定是一起的,不行变的。

一般情况下,当我们对order表进行分表的时分,比如我们要分红128张表的话,那么得到的128表应该是:order_0000、order_0001、order_0002…..order_0126、order_0127

一般的分表算法有以下几种:

直接取模

在分库分表时,我们是事前可以知道要分红多少个库和多少张表的,所以,比较简略的就是取模的办法。

比如我们要分红128张表的话,就用一个整数来对128取模就行了,得到的效果假设是0002,那么就把数据放到order_0002这张表中。

比如表中有一万条数据,我们拆分为两张表,id 为奇数的:1,3,5,7……放在 user1 中, id 为偶数的:2,4,6,8……放在 user2 中,这样的拆分办法就是水平拆分了。

Hash取模

那假设分表字段不是数字类型,而是字符串类型怎样办呢?有一个办法就是哈希取模,就是先对这个分表字段取Hash,然后在再取模。

但是需求注意的是,Java中的hash办法得到的效果有或许是负数,需求考虑这种负数的情况。

一起性Hash

前面两种取模办法都比较不错,可以使我们的数据比较均匀的分布到多张分表中。但是仍是存在一个缺陷。

那就是假设需求扩容二次分表,表的总数量产生变化时,就需求从头核算hash值,就需求涉及到数据搬家了。

为了处理扩容的问题,我们可以选用一起性哈希的办法来做分表。

总算有人能把分库分表讲理解了

一起性哈希可以按照常用的hash算法来将对应的key哈希到一个具有2^32次方个节点的空间中,构成成一个顺时针首尾相接的闭合的环形。所以当增加一台新的数据库服务器时,只需增加服务器的方位和逆时针方向第一台服务器之间的键会受影响。

全局ID的生成

涉及到分库分表,就会引申出分布式系统中仅有主键ID的生成问题,因为在单表中我们可以用数据库主键来做仅有ID,但是假设做了分库分表,多张单表中的自增主键就必定会产生抵触。那就不具备全局仅有性了。

那么,怎样生成一个全局仅有的ID呢?有以下几种办法:

UUID

许多人对UUID都不陌生,它是可以做到全局仅有的,而且生成办法也简略,但是我们一般不推荐运用他做仅有ID,首要UUID太长了,其次字符串的查询功率也比较慢,而且没有事务含义,根柢看不懂。

依据某个单表做自增主键

多张单表生成的自增主键会抵触,但是假设全部的表中的主键都从同一张表生成是不是就可以了。

全部的表在需求主键的时分,都到这张表中获取一个自增的ID。

这样做是可以做到仅有,也能完成自增,但是问题是这个单表就变成整个系统的瓶颈,而且也存在单点问题,一旦他挂了,那整个数据库就都无法写入了。

依据多个单表+步长做自增主键

为了处理单个数据库做自曾主键的瓶颈及单点故障问题,我们可以引进多个表来一起生成就行了。

但是怎样确保多张表里边生成的Id不重复呢?假设我们能完成以下的生成办法就行了:

实例1生成的ID从1000初步,到1999结束。

实例2生成的ID从2000初步,到2999结束。

实例3生成的ID从3000初步,到3999结束。

实例4生成的ID从4000初步,到4999结束。

总算有人能把分库分表讲理解了

这样就能防止ID重复了,那假设第一个实例的ID现已用到1999了怎样办?那就生成一个新的起始值:

实例1生成的ID从5000初步,到5999结束。实例2生成的ID从6000初步,到6999结束。实例3生成的ID从7000初步,到7999结束。实例4生成的ID从8000初步,到8999结束。

我们把步长设置为1000,确保每一个单表中的主键起始值都不相同,而且比当时的最大值相差1000就行了。

雪花算法

雪花算法也是比较常用的一种分布式ID的生成办法,它具有全局仅有、递加、高可用的特色。

雪花算法生成的主键首要由 4 部分组成,1bit符号位、41bit时间戳位、10bit作业进程位以及 12bit 序列号位。

时间戳占用41bit,准确到毫秒,一共可以包容约69年的时间。

作业进程位占用10bit,其间高位5bit是数据中心ID,低位5bit是作业节点ID,做多可以包容1024个节点。

序列号占用12bit,每个节点每毫秒0初步不断累加,最多可以累加到4095,一共可以产生4096个ID。

所以,一个雪花算法可以在同一毫秒内最多可以生成1024 X 4096 = 4194304个仅有的ID

分库分表的东西

在选定了分表字段和分表算法之后,那么,怎样把这些功用给完成出来,需求怎样做呢?

我们怎样可以做到像处理单表相同处理分库分表的数据呢?这就需求用到一个分库分表的东西了。

目前市面上比较不错的分库分表的开源结构首要有三个,分别是sharding-jdbc、TDDL和Mycat

Sharding-JDBC

现在叫ShardingSphere(Sharding-JDBC、Sharding-Proxy和Sharding-Sidecar这3款彼此独立的产品组成)。它定位为轻量级Java结构,在Java的JDBC层供应的额外服务。它运用客户端直连数据库,以jar包方式供应服务,无需额外布置和依托,可理解为增强版的JDBC驱动,完全兼容JDBC和各种ORM结构。

开原地址:shardingsphere.apache.org

TDDL

TDDL 是淘宝开源的一个用于访问数据库的中间件, 它集成了分库分表, 读写别离,权重分配,动态数据源配备等功用。封装 jdbc 的 DataSource给用户供应一起的依据客户端的运用。

开源地址:github.com/alibaba/tb_…

Mycat

Mycat是一款分布式联系型数据库中间件。它支撑分布式SQL查询,兼容MySQL通信协议,以Java生态支撑多种后端数据库,通过数据分片进步数据查询处理才干。

开源地址:github.com/MyCATApache…

分库分表带来的复杂性

分库分表之后,会带来许多问题。

首要,做了分库分表之后,全部的读和写操作,都需求带着分表字段,这样才干知道详细去哪个库、哪张表中去查询数据。假设不带的话,就得支撑全表扫描。

但是,单表的时分全表扫描比较简略,但是做了分库分表之后,就没办法做扫表的操作了,假设要扫表的话就要把全部的物理表都要扫一遍。

还有,一旦我们要从多个数据库中查询或许写入数据,就有许多事情都不能做了,比如跨库事务就是不支撑的。

总算有人能把分库分表讲理解了

所以,分库分表之后就会带来因为不支撑事务而导致的数据一起性的问题。

其次,做了分库分表之后,从前单表中很便利的分页查询、排序等等操作就都失效了。因为我们不能跨多表进行分页、排序。

总之,分库分表虽然能处理一些大数据量、高并发的问题,但是一起也会带来一些新的问题。所以,在做数据库优化的时分,仍是主张我们优先选择其他的优化办法,终究再考虑分库分表。

跨库相关查询

在单库未拆分表之前,我们可以很便利运用 join 操作相关多张表查询数据,但是通过分库分表后两张表或许都不在一个数据库中,怎样运用 join 呢?

有几种计划可以处理:

  • 字段冗余:把需求相关的字段放入主表中,防止 join 操作;
  • 数据笼统:通过 ETL 等将数据集合集合,生成新的表;
  • 全局表:比如一些基础表可以在每个数据库中都放一份;
  • 运用层组装:将基础数据查出来,通过运用程序核算组装;
  • 冗余事务表。”A库的a表需求与B库的b表相关查询“场景,可以在A库中冗余存储B库的b表,通过数据同步机制坚持与B库的b表数据一起,这样A库就可以直接相关冗余表了。
  • 绑定表。需求相关的数据放在一个节点上。比如按租户分库分表,不同租户之间不会有数据相关查询,同一个租户内的数据都在一个节点上,可以直接相关。 当出现了跨接待你相关的问题,必定要想一想事务逻辑是否合理,假设真的要跨节点相关,一般是通过rpc调用其他一个节点的数据,组装好再回来给前端或许第三方,对外无感知。

分布式事务

单数据库可以用本地事务搞定,运用多数据库就只能通过分布式事务处理了。

常用处理计划有:依据可靠消息(MQ)的处理计划、两阶段事务提交、柔性事务等。

排序、分页、函数核算问题

在运用 SQL 时 order by、limit 等要害字需求特别处理,一般来说选用分片的思路:

先在每个分片上实行相应的函数,然后将各个分片的效果集进行汇总和再次核算,终究得到效果。

跨节点多库进行查询时,会出现 limit 分页,order by 排序的问题。比如有两个节点,节点 1 存的是奇数 id=1,3,5,7,9……;节点 2 存的是偶数 id=2,4,6,8,10……实行 select * from user_info order by id limit 0, 10,则需求在两个节点上各取出 10 条,然后兼并数据,从头排序。

max、min、sum、count 之类的函数在进行核算的时分,也需求先在每个分片上实行相应的函数,然后将各个分片的效果集进行汇总和再次核算,终究将效果回来。

或许依据称号迷糊分页查询,而称号字段单独存放在表,这个时分就需求先从表中查出全部符合迷糊查询的记录,然后从主表中分页查询,当表非常大的时分很耗功用。

分布式 ID

假设运用 Mysql 数据库在单库单表可以运用 id 自增作为主键,分库分表了之后就不行了,会出现 id 重复。

常用的分布式 ID 处理计划有:

  • UUID
  • 依据数据库自增单独维护一张 ID表
  • 号段方式
  • Redis 缓存
  • 雪花算法(Snowflake)
  • 百度 uid-generator
  • 美团 Leaf
  • 滴滴 Tinyid

多数据源

分库分表之后或许会面临从多个数据库或多个子表中获取数据,一般的处理思路有:客户端适配和代理层适配。

业界常用的中间件有:

  • shardingsphere(前身 sharding-jdbc)
  • Mycat

数据库扩容、数据搬家问题

有的分库分表战略在数据库扩容的时分不便利。比如按id的奇偶分表,当某一天按奇偶分表仍是太大,需求换再加一张表,则需求换一种分表战略,比如模3或许模5,则需求把之前的两张表数据读取出来从头分表;

有的表按月份分表,每个月增加一张表,则不需求搬家前史数据。

假设选用数值范围分片,只需求增加节点就可以进行扩容了,不需求对分片数据搬家。假设选用的是数值取模分片,则考虑后期的扩容问题就相对比较费事。

参阅文献:

《再有人问你什么是分库分表,直接把这篇文章发给他》www.51cto.com/article/709…

《什么时分进行MySQL分库和分表?》www.cnblogs.com/wangyongwen…