面试题待整理

1. 为什么要分库分表

业务高速增长,单表每月增量很大,qps很高,磁盘消耗加剧

1) 分表

单表几千万数据,数据量太大,影响到了sql的执行性能,一般几百万的时候,性能就会差一点,就得分表了。

分表就是一个表的数据放到多个表中,查询的时候就查一个表,比如按照用户id分表,将一个用户的数据放到一个表中,控制每个表的数据量的大小,比如每个表固定在200w以内

2) 分库

一般一个库最大并发2000,就需要扩容了,一个健康的库并发保持在1000/s,不要太大。
如果并发太大,就可以将一个库的数据拆分到多个库中

3) 分库分表优点

并发支撑:没分之前,单机部署,扛不住高并发;分了之后,承受的并发增加了多倍
磁盘使用:没分之前单机磁盘容量几乎撑满;分了之后,磁盘使用率大大降低
sql性能:没分之前单表数据量大,sql越跑越慢;分了之后单表数据量减少,sql效率提升

2. 有哪些分库分表中间件

1) Cobar

阿里b2b团队开发和开源的,属于proxy层方案,介于应用服务器和数据库之间。

应用程序通过jdbc驱动访问Cobar集群,Cobar根据sql和分库规则对sql分解,分发到mysql集群的不同数据库实例上。

早几年有人用,最近几年没更新,基本没啥人使用了,差不多被抛弃的状态

不支持读写分离、存储过程、夸库join、分页

2) TDDL

淘宝团队开发的,属于 client 层方案。支持基本的 crud 语法和读写分离,但不支持 join、多表查询等语法。目前使用的也不多,因为还依赖淘宝的 diamond 配置管理系统。

3) Atlas

360 开源的,属于 proxy 层方案,以前是有一些公司在用的,但是确实有一个很大的问题就是社区最新的维护都在 5 年前了。所以,现在用的公司基本也很少了。

4) ShardingSphere

当当开源的,属于 client 层方案,目前已经更名为 ShardingSphere(后文所提到的 Sharding-jdbc,等同于 ShardingSphere)。确实之前用的还比较多一些,因为 SQL 语法支持也比较多,没有太多限制,而且截至 2019.4,已经推出到了 4.0.0-RC1 版本,支持分库分表、读写分离、分布式 id 生成、柔性事务(最大努力送达型事务、TCC 事务)。而且确实之前使用的公司会比较多一些(这个在官网有登记使用的公司,可以看到从 2017 年一直到现在,是有不少公司在用的),目前社区也还一直在开发和维护,还算是比较活跃,个人认为算是一个现在也可以选择的方案。

Sharding-jdbc 这种 client 层方案的优点在于不用部署,运维成本低,不需要代理层的二次转发请求,性能很高,但是如果遇到升级啥的需要各个系统都重新升级版本再发布,各个系统都需要耦合 Sharding-jdbc 的依赖;

5) Mycat

基于 Cobar 改造的,属于 proxy 层方案,支持的功能非常完善,而且目前应该是非常火的而且不断流行的数据库中间件,社区很活跃,也有一些公司开始在用了。但是确实相比于 Sharding jdbc 来说,年轻一些,经历的锤炼少一些。

Mycat 这种 proxy 层方案的缺点在于需要部署,自己运维一套中间件,运维成本高,但是好处在于对于各个项目是透明的,如果遇到升级之类的都是自己中间件那里搞就行了。

6) 使用建议

中小型公司选用 Sharding-jdbc,client 层方案轻便,而且维护成本低,不需要额外增派人手,而且中小型公司系统复杂度会低一些,项目也没那么多

但是中大型公司最好还是选用 Mycat 这类 proxy 层方案,因为可能大公司系统和项目非常多,团队很大,人员充足,那么最好是专门弄个人来研究和维护 Mycat,然后大量项目直接透明使用即可。

3. 什么是水平拆分、垂直拆分

水平拆分的意思,就是把一个表的数据给弄到多个库的多个表里去,但是每个库的表结构都一样,只不过每个库表放的数据是不同的,所有库表的数据加起来就是全部数据。

水平拆分的意义,就是将数据均匀放更多的库里,然后用多个库来扛更高的并发,还有就是用多个库的存储容量来进行扩容。

垂直拆分的意思,就是把一个有很多字段的表给拆分成多个表,或者是多个库上去。每个库表的结构都不一样,每个库表都包含部分字段。一般来说,会将较少的访问频率很高的字段放到一个表里去,然后将较多的访问频率很低的字段放到另外一个表里去。

因为数据库是有缓存的,你访问频率高的行字段越少,就可以在缓存里缓存更多的行,性能就越好。这个一般在表层面做的较多一些。

还有表层面的拆分,就是分表,将一个表变成 N 个表,就是让每个表的数据量控制在一定范围内,保证 SQL 的性能。否则单表数据量越大,SQL 性能就越差。一般是 200 万行左右,不要太多,但是也得看具体你怎么操作,也可能是 500 万,或者是 100 万。你的SQL越复杂,就最好让单表行数越少。

4. 分库分表的方式

一种是按照 range 来分,就是每个库一段连续的数据,这个一般是按比如时间范围来的,但是这种一般较少用,因为很容易产生热点问题,大量的流量都打在最新的数据上了。

或者是按照某个字段 hash 一下均匀分散,这个较为常用。

range 来分,好处在于说,扩容的时候很简单,因为你只要预备好,给每个月都准备一个库就可以了,到了一个新的月份的时候,自然而然,就会写新的库了;
缺点,但是大部分的请求,都是访问最新的数据。实际生产用 range,要看场景。

hash 分发,好处在于说,可以平均分配每个库的数据量和请求压力;坏处在于说扩容起来比较麻烦,会有一个数据迁移的过程,之前的数据需要重新计算 hash 值重新分配到不同的库或表。

5. 分库分表数据迁移方案

1) 停机迁移

挂个公告0点-早上6点网站停止访问,开始运维升级,无法访问。

接着到时间点了,停止流量接入。

运行迁移数据的工具,把老数据导入到新的分库分表上。

将修改的代码和配置,重新发布上线。

验证是否ok。

2) 双写迁移

6. 分库分表动态扩容方案

1) 设定好几台数据库服务器,每台服务器上几个库,每个库多少个表,推荐是 32 库 * 32 表,对于大部分公司来说,可能几年都够了。

2) 路由的规则,orderId 模 32 = 库,orderId / 32 模 32 = 表

3) 扩容的时候,申请增加更多的数据库服务器,装好 mysql,呈倍数扩容,4 台服务器,扩到 8 台服务器,再到 16 台服务器。

4) 由 dba 负责将原先数据库服务器的库,迁移到新的数据库服务器上去,库迁移是有一些便捷的工具的。

5) 我们这边就是修改一下配置,调整迁移的库所在数据库服务器的地址。

6) 重新发布系统,上线,原先的路由规则变都不用变,直接可以基于 n 倍的数据库服务器的资源,继续进行线上系统的提供服务。

7. 如何计算b+树可以存放多少条数据

叶子节点假设一行记录是1kb,一页16kb,可以放16行数据

非叶子节点
一个BigInt主键8字节、一个指针6字节,一个kv14字节
那么一个非叶子节点可以存放16kb/14byte=1170个 kv

高度为2的情况下,只有一个根节点,那么能存储1170 * 16=18724条数据

高度为3的情况下,根节点1170条数据,每条对应一个页(1170 16),那么就是能放1368900页数据(136w页),对应到行就是1170 1170 * 16 = 21902400 接近2200w条数据

高度为4的情况下,1170 1170 1170 16 =1 601 613 000 16 =16亿页 * 16 =256亿

总结:
1. 聚簇索引按主键是bigint算,一个bigint 8字节,一个指针6字节,一条kv=14字节
2. 一页数据16kb,16kb/14字节=1170行
3. 也就是一层非叶子节点能放1170页数据
4. 两层非叶子节点能放11701170=136w页数据
5. 三层非叶子节点能放1170
11701170=160亿页数据
6. 一页数据按1kb一行,16kb能放16,拿页数
一页能放的行数就是总记录数