前    言
本文仅收录了一些常见的分库分表面试题,如需查看其它java面试题可查看我另一篇博文:

JAVA | 2021最全Java面试题及答案汇总


正    文

1.为什么要分库分表

跟着你的公司业务发展走的,你公司业务发展越好,用户就越多,数据量越大,请求量
越大,那你单个数据库一定扛不住。
比如你单表都几千万数据了,你确定你能抗住么?绝对不行,单表数据量太大,会极
大影响你的 sql 执行的性能,到了后面你的 sql 可能就跑的很慢了。一般来说,就以我的经
验来看,单表到几百万的时候,性能就会相对差一些了,你就得分表了。
分表是啥意思?就是把一个表的数据放到多个表中,然后查询的时候你就查一个表。
比如按照用户 id 来分表,将一个用户的数据就放在一个表中。然后操作的时候你对一个用
户就操作那个表就好了。这样可以控制每个表的数据量在可控的范围内,比如每个表就固定
在 200 万以内。
分库是啥意思?就是你一个库一般我们经验而言,最多支撑到并发 2000,一定要扩容
了,而且一个健康的单库并发值你最好保持在每秒 1000 左右,不要太大。那么你可以将一
个库的数据拆分到多个库中,访问的时候就访问一个库好了。

2.用过哪些分库分表中间件?

比较常见的包括:cobar、TDDL、atlas、sharding-jdbc、mycat

3.不同的分库分表中间件都有什么优点和缺点?

sharding-jdbc 这种 client 层方案的优点在于不用部署,运维成本低,不需要代理层的二
次转发请求,性能很高,但是如果遇到升级啥的需要各个系统都重新升级版本再发布,各个
系统都需要耦合 sharding-jdbc 的依赖;
mycat 这种 proxy 层方案的缺点在于需要部署,自己及运维一套中间件,运维成本高,
但是好处在于对于各个项目是透明的,如果遇到升级之类的都是自己中间件那里搞就行了。

4.你们具体是如何对数据库如何进行垂直拆分或水 平拆分的?

水平拆分的意思,就是把一个表的数据给弄到多个库的多个表里去,但是每个库的表结
构都一样,只不过每个库表放的数据是不同的,所有库表的数据加起来就是全部数据。水平
拆分的意义,就是将数据均匀放更多的库里,然后用多个库来抗更高的并发,还有就是用多
个库的存储容量来进行扩容。
垂直拆分的意思,就是把一个有很多字段的表给拆分成多个表,或者是多个库上去。
每个库表的结构都不一样,每个库表都包含部分字段。一般来说,会将较少的访问频率很高
的字段放到一个表里去,然后将较多的访问频率很低的字段放到另外一个表里去。因为数据
库是有缓存的,你访问频率高的行字段越少,就可以在缓存里缓存更多的行,性能就越好。
这个一般在表层面做的较多一些。
你的项目里该如何分库分表?一般来说,垂直拆分,你可以在表层面来做,对一些字段
特别多的表做一下拆分;水平拆分,你可以说是并发承载不了,或者是数据量太大,容量承
载不了,你给拆了,按什么字段来拆,你自己想好;分表,你考虑一下,你如果哪怕是拆到
每个库里去,并发和容量都 ok 了,但是每个库的表还是太大了,那么你就分表,将这个表
分开,保证每个表的数据量并不是很大。
而且这儿还有两种分库分表的方式,一种是按照 range 来分,就是每个库一段连续的数
据,这个一般是按比如时间范围来的,但是这种一般较少用,因为很容易产生热点问题,大
量的流量都打在最新的数据上了;或者是按照某个字段 hash 一下均匀分散,这个较为常用。

5.现在有一个未分库分表的系统,未来要分库分表, 如何设计才可以让系统从未分库分表动态切换到分 库分表上?

简单来说,就是在线上系统里面,之前所有写库的地方,增删改操作,都除了对老库增
删改,都加上对新库的增删改,这就是所谓双写,同时写俩库,老库和新库。
然后系统部署之后,新库数据差太远,用之前说的导数工具,跑起来读老库数据写新
库,写的时候要根据 gmt_modified 这类字段判断这条数据最后修改的时间,除非是读出来
的数据在新库里没有,或者是比新库的数据新才会写。
接着导万一轮之后,有可能数据还是存在不一致,那么就程序自动做一轮校验,比对
新老库每个表的每条数据,接着如果有不一样的,就针对那些不一样的,从老库读数据再次
写。反复循环,直到两个库每个表的数据都完全一致为止。
接着当数据完全一致了,就 ok 了,基于仅仅使用分库分表的最新代码,重新部署一次,
不就仅仅基于分库分表在操作了么,还没有几个小时的停机时间,很稳。所以现在基本玩儿
数据迁移之类的,都是这么干了。

6.如何设计可以动态扩容缩容的分库分表方案?

一开始上来就是 32 个库,每个库 32 个表,1024 张表
我可以告诉各位同学说,这个分法,第一,基本上国内的互联网肯定都是够用了,第
二,无论是并发支撑还是数据量支撑都没问题, 每个库正常承载的写入并发量是 1000,那么
32 个库就可以承载 32 * 1000 = 32000 的写并发,如果每个库承载 1500 的写并发,32 * 1500
= 48000 的写并发,接近 5 万/s 的写入并发,前面再加一个 MQ,削峰,每秒写入 MQ 8 万
条数据,每秒消费 5 万条数据。
有些除非是国内排名非常靠前的这些公司,他们的最核心的系统的数据库,可能会出
现几百台数据库的这么一个规模,128 个库,256 个库,512 个库 1024 张表,假设每个表放
500 万数据,在 MySQL 里可以放 50 亿条数据 每秒的 5 万写并发,总共 50 亿条数据,对于
国内大部分的互联网公司来说,其实一般来说都够了 谈分库分表的扩容,第一次分库分表,
就一次性给他分个够,32 个库,1024 张表,可能对大部分的中小型互联网公司来说,已经
可以支撑好几年了 一个实践是利用 32 * 32 来分库分表,即分为 32 个库,每个库里一个表
分为 32 张表。一共就是 1024 张表。根据某个 id 先根据 32 取模路由到库,再根据 32 取模
路由到库里的表。
刚开始的时候,这个库可能就是逻辑库,建在一个数据库上的,就是一个 mysql 服务器
可能建了 n 个库,比如 16 个库。后面如果要拆分,就是不断在库和 mysql 服务器之间做迁
移就可以了。然后系统配合改一下配置即可。

7.你们有没有做 MySQL 读写分离?如何实现 mysql 的 读写分离?MySQL 主从复制原理的是啥?如何解决 mysql 主从同步的延时问题?

其实很简单,就是基于主从复制架构,简单来说,就搞一个主库,挂多个从库,然后我
们就单单只是写主库,然后主库会自动把数据给同步到从库上去。
主库将变更写 binlog 日志,然后从库连接到主库之后,从库有一个 IO 线程,将主库的
binlog 日志拷贝到自己本地,写入一个中继日志中。接着从库中有一个 SQL 线程会从中继日
志读取 binlog,然后执行 binlog 日志中的内容,也就是在自己本地再次执行一遍 SQL,这样
就可以保证自己跟主库的数据是一样的。
这里有一个非常重要的一点,就是从库同步主库数据的过程是串行化的,也就是说主
库上并行的操作,在从库上会串行执行。所以这就是一个非常重要的点了,由于从库从主库
拷贝日志以及串行执行 SQL 的特点,在高并发场景下,从库的数据一定会比主库慢一些,
是有延时的。所以经常出现,刚写入主库的数据可能是读不到的,要过几十毫秒,甚至几百
毫秒才能读取到。
要考虑好应该在什么场景下来用这个 mysql 主从同步,建议是一般在读远远多于写,而
且读的时候一般对数据时效性要求没那么高的时候,用 mysql 主从同步。通常来说,我们会
对于那种写了之后立马就要保证可以查到的场景,采用强制读主库的方式。如果主从延迟较
为严重,分库,将一个主库拆分为 4 个主库,每个主库的写并发就 500/s,此时主从延迟可
以忽略不计;打开 mysql 支持的并行复制,多个库并行复制

8.分库分表之后,id 主键如何处理?

数据库自增 id,
这个就是说你的系统里每次得到一个 id,都是往一个库的一个表里插入一条没什么业
务含义的数据,然后获取一个数据库自增的一个 id。拿到这个 id 之后再往对应的分库分表
里去写入。
这个方案的好处就是方便简单,谁都会用;缺点就是单库生成自增 id,要是高并发的
话,就会有瓶颈的;如果你硬是要改进一下,那么就专门开一个服务出来,这个服务每次就
拿到当前 id 最大值,然后自己递增几个 id,一次性返回一批 id,然后再把当前最大 id 值
修改成递增几个 id 之后的一个值;但是无论如何都是基于单个数据库。
UUID
好处就是本地生成,不要基于数据库来了;不好之处就是,UUID 太长了,作为主键性
能太差了,另外 UUID 不具有有序性,会造成 B+ 树索引在写的时候有过多的随机写操作,
频繁修改树结构,从而导致性能下降。
snowflake 算法

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐