写在前面

什么时候需要分库分表,这是一个很常见的面试题目,今天我们就来讲一下为什么会进行分库分表,怎么分库分表?

前排提示:在系统架构设计中,一定要避免过度设计或者过度的未来想象! 所有的技术是服务于业务的,不是说用了最复杂的技术,业务就会好,相反会因为维护成本与业务利润不成正比而造成负面影响。适合业务本身的技术,才是好技术。不要每天个位数的QPS也做分库分表,个位数的QPS也上微服务啥的,没必要。

同名B站账号上有这期视频,也欢迎大家到B站看视频版!

MySQL索引

ok,那我们就进入主题吧,首先我们要有一个概念,为什么需要分库分表?

大部分公司都会使用mysql作为存储介质,但磁盘的随机读写是很慢的,所以mysql中有一个很重要的概念叫索引索引能加速mysql的查找速度,如果没有索引,那么mysql就会进行全表扫描,全表扫就很慢了。

一般我们会用 InnoDB作为存储引擎,其中索引结构是B+树。B+树结构图如下:

B+树结构

叶子节点存储数据,非叶子节点存储键值+指针,索引组织表通过非叶子节点的指针确定数据在哪个页中,进而再去数据页中找到需要的数据。当我们mysql的单表到了一定的数据量级之后,这颗B+树就变得越来越宽,甚至越来越高,此时多余的io操作就会导致查询速度变慢。

那么这个一定数量级是多少呢?1kw,3kw?5kw?

阿里巴巴Java开发手册上写的是500w行就做分库分表,当然国内也没多少个软件能达到淘天这样的并发数和财力。为了兼容成本,我们一般的服务到多少才做分库分表呢?我们可以来算一下,InnoDB存储引擎,最小储存单元是页,一页大小是16kB+树的叶子节点存的是数据,非叶子节点存的是键值+指针。假设我们走的是主键索引,假设现在B+树的高度为2,即有一个根结点和若干个叶子结点。根节点的每一个指针都对应下一层的某个叶子节点,所以这棵高度为2的B+树的能存放的数据总条数 = 根结点指针数*单个叶子节点记录行数。 如果一行数据大小为1k,那么单个叶子节点可以存的记录数 = 16k/1k = 16个,如果主键ID为 bigint类型,长度为8字节, 而指针大小在InnoDB源码中设置为6字节,所以主键+指针就是 8+6=14 字节,一个根节点可以存储 16k/14B =16*1024B/14B = 1170 个元素

因此,一棵高度为2的B+树,能存放 1170 * 16=18720 条 这样的数据记录,同理一棵高度为3的B+树,能存放 1170 * 1170 *16 =21902400,可以存放两千万左右的数据。B+树高度一般为1-3层,如果B+到了4层,查询就会出现多次的磁盘IO,SQL就会变慢了。

所以我们可以设定3kw数据量后,单表数据量太大,B+数变高,IO查询次数过多,SQL查询会变慢,需要考虑进行分表。

这里要注意一点,不是什么数据都要做分库分表的,是一些重要的数据才分库分表,比如订单表、商品表等和核心业务强相关的表,如果是一些日志表,这种成本表,就不需要分库分表,直接同步到像tidb、starrocks这种分布式数据库,省事。

分表

具体怎么分表呢?我们需要结合当前现状,估算未来业务的发展。

比如当前业务工作日的订单量是百万级,并且周末、节假日会有增加,所以综合起来,每天的订单是150w,那么一年就有150w*365 = 54750w 单,我们估算未来10年的订单,也就是会有 547500w 单,这个数除3kw = 182

也就是说其实单库的183分表就可以解决未来10年,每天150万订单的数据量。一般我们会用2的倍数来做分表、比如128分表、256分表等等,所以未来10年,每天百万订单的256分表绰绰有余了。

那我们怎么知道某条数据应该落在哪里呢?这就涉及到分片键,我们先有一个概念,我们分库分表是为了处理大数据,而大数据大概率会出现在C端产品,而C端产品一般都是以用户为中心。大家想想,自己用淘宝、美团、或者滴滴的时候,是不是看到的都是自己的信息,比如订单信息,而不是别人的信息,所以C端产品的分库分表一般会使用用户的id后几位做取余,如果是256分表,就会对256做取余。

举个例子,某个用户的id为666,那么对256做取余后,结果为666%256 = 154,也就会落到 154分表中。我们的数据都会有dts同步到类似tidb这种分布式数据库,所有的数据tidb都有,如果出现了分片键没有命中的场景,会去tidb查,做最后的兜底。

99%的业务场景都可以用分片键命中,如果PM提出的需求,RD评估起来觉得没法用分片建命中,可以和业务方沟通甚至直接拒掉!

分库

那什么时候分库呢?单库的256分表已经能解决未来10年每天百万订单的数据。但如果像淘宝、滴滴、美团这种每天千万订单的数据,单库256分表是不可能存储住的。

我们的业务也不会只有订单表,还会其他的业务表。如果都放单库,一个库就会有上千张,这是不合理的设计,并且单库的连接数是有限制的。这个时候就需要分库了,将上面的256分表分到不同的库中存储,一个库存储64张或者32张。这样就能平均其他业务表的数量,也能均衡连接数和带宽。 同样的也可以用用户唯一id标识后四位,做分偏键取余,路由到哪个库的哪张分表中。

最后补充一点:为了容灾,DBA会备多主多从的结构,读操作,直接读从库,写操作,直接写主库,然后做主从同步

参考

https://cloud.tencent.com/developer/article/2329451
https://segmentfault.com/a/1190000041883661