前语

咱们好,我是田螺。咱们去面试的时分,简直都会被问到分库分表。田螺哥整理了分库分表的15道经典分库分表面试题,咱们看完肯定会有协助的。

面试必备:分库分表经典15连问

  • 公众号:捡田螺的小男孩
  • github地址,感谢每颗star:github

1. 咱们为什么需求分库分表

在分库分表之前,就需求考虑为什么需求拆分。咱们做一件事,肯定是有充分理由的。所以得想好分库分表的理由是什么。咱们现在就从两个维度去考虑它,为什么要分库?为什么要分表?

1.1 为什么要分库

假如事务量剧增,数据库或许会出现功能瓶颈,这时分咱们就需求考虑拆分数据库。从这两方面来看:

  • 磁盘存储

事务量剧增,MySQL单机磁盘容量会撑爆,拆成多个数据库,磁盘运用率大大降低。

  • 并发衔接支撑

咱们知道数据库衔接数是有限的。在高并发的场景下,很多恳求拜访数据库,MySQL单机是扛不住的!高并发场景下,会出现too many connections报错。

当时十分火的微服务架构出现,便是为了应对高并发。它把订单、用户、产品等不同模块,拆分红多个运用,并且把单个数据库也拆分红多个不同功能模块的数据库(订单库、用户库、产品库),以分担读写压力。

1.2 为什么要分表

假定你的单表数据量十分大,存储和查询的功能就会遇到瓶颈了,假如你做了很多优化之后仍是无法提高功率的时分,就需求考虑做分表了。一般千万等级数据量,就需求分表。

这是由于即便SQL命中了索引,假如表的数据量超越一千万的话,查询也是会显着变慢的。这是由于索引一般是B+树结构,数据千万等级的话,B+树的高度会增高,查询就变慢啦。MySQL的B+树的高度怎样核算的呢?跟咱们复习一下:

InnoDB存储引擎最小贮存单元是页,一页巨细便是16k。B+树叶子存的是数据,内部节点存的是键值+指针。索引组织表经过非叶子节点的二分查找法以及指针确定数据在哪个页中,从而再去数据页中找到需求的数据,B+树结构图如下:

面试必备:分库分表经典15连问

假定B+树的高度为2的话,即有一个根结点和若干个叶子结点。这棵B+树的寄存总记载数为=根结点指针数*单个叶子节点记载行数。

假如一行记载的数据巨细为1k,那么单个叶子节点能够存的记载数 =16k/1k =16. 非叶子节点内寄存多少指针呢?咱们假定主键ID为bigint类型,长度为8字节(面试官问你int类型,一个int便是32位,4字节),而指针巨细在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层,查询的时分会多查磁盘的次数,SQL就会变慢。

因而单表数据量太大,SQL查询会变慢,所以就需求考虑分表啦。

2. 什么时分考虑分库分表?

对于MySQLInnoDB存储引擎的话,单表最多能够存储10亿级数据。可是的话,假如真的存储这么多,功能就会十分差。一般数据量千万等级,B+树索引高度就会到3层以上了,查询的时分会多查磁盘的次数,SQL就会变慢。

阿里巴巴的Java开发手册》提出:

单表行数超越500万行或者单表容量超越2GB,才引荐进行分库分表。

那咱们是不是等到数据量到达五百万,才开端分库分表呢?

不是这样的,咱们应该提前规区分库分表,假如预算3年后,你的表都不会到达这个五百万,则不需求分库分表。

MySQL服务器假如装备更好,是不是能够超越这个500万这个量级,才考虑分库分表?

尽管装备更好,或许数据量大之后,功能仍是不错,可是假如持续发展的话,仍是要考虑分库分表

一般什么类型事务表需求才分库分表?

通用是一些流水表、用户表等才考虑分库分表,假如是一些装备类的表,则完全不用考虑,由于不太或许到达这个量级。

3. 怎么挑选分表键

分表键,即用来分库/分表的字段,换种说法便是,你以哪个维度来分库分表的。比方你按用户ID分表、按时刻分表、按区域别表,这些用户ID、时刻、区域便是分表键。

一般数据库表拆分的原则,需求先找到事务的主题。比方你的数据库表是一张企业客户信息表,就能够考虑用了客户号做为分表键。

为什么考虑用客户号做分表键呢?

这是由于表是根据客户信息的,所以,需求将同一个客户信息的数据,落到一个表中,防止触发全表路由

4.分外表键怎么查询

分库分表后,有时分无法防止一些事务场景,需求经过分外表键来查询

假定一张用户表,根据userId做分表键,来分库分表。可是用户登录时,需求根据用户手机号来登陆。这时分,就需求经过手机号查询用户信息。而手机号是分外表键

分外表键查询,一般有这几种计划:

  • 遍历:最粗犷的办法,便是遍历一切的表,找出符合条件的手机号记载(不主张
  • 将用户信息冗余同步到ES,同步发送到ES,然后经过ES来查询(引荐

其实还有基因法:比方分外表键能够解分出分表键出来,比方常见的,订单号生成时,能够包括客户号进去,经过订单号查询,就能够解分出客户号。可是这个场景在外,手机号似乎不适合冗余userId

5. 分表战略怎么挑选

5.1 range规模

range,即规模战略区分表。比方咱们能够将表的主键order_id,依照从0~300万的区分为一个表,300万~600万区分到另外一个表。如下图:

面试必备:分库分表经典15连问

有时分咱们也能够按时刻规模来区分,如不同年月的订单放到不同的表,它也是一种range的区分战略。

  • 长处: Range规模分表,有利于扩容。
  • 缺陷: 或许会有热门问题。由于订单id是一直在增大的,也便是说最近一段时刻都是汇聚在一张表里边的。比方最近一个月的订单都在300万~600万之间,平时用户一般都查最近一个月的订单比较多,恳求都打到order_1表啦。

5.2 hash取模

hash取模战略:

指定的路由key(一般是user_id、order_id、customer_no作为key)对分表总数进行取模,把数据涣散到各个表中。

比方原始订单表信息,咱们把它分红4张分表:

面试必备:分库分表经典15连问

  • 比方id=1,对4取模,就会得到1,就把它放到t_order_1;
  • id=3,对4取模,就会得到3,就把它放到t_order_3;

一般,咱们会取哈希值,再做取余

Math.abs(orderId.hashCode()) % table_number
  • 长处:hash取模的方式,不会存在显着的热门问题
  • 缺陷:假如未来某个时分,表数据量又到瓶颈了,需求扩容,就比较麻烦。所以一般主张提前规划好,一次性分够。(能够考虑一致性哈希

5.3 一致性Hash

假如用hash方式分表,前期规划欠好,需求扩容二次分表,表的数量需求添加,所以hash值需求重新核算,这时分需求搬迁数据了。

比方咱们开端分了10张表,之后事务扩展需求,添加到20张表。那问题就来了,之前根据orderId取模10后的数据涣散在了各个表中,现在需求重新对一切数据重新取模20来分配数据

为了处理这个扩容搬迁问题,能够运用一致性hash思想来处理。

一致性哈希:在移除或者添加一个服务器时,能够尽或许小地改变已存在的服务恳求与处理恳求服务器之间的映射联系。一致性哈希处理了简略哈希算法在分布式哈希表存在的动态弹性等问题

6. 怎么防止热门问题数据歪斜(热门数据)

假如咱们根据时刻规模分片,某电商公司11月搞营销活动,那么大部分的数据都落在11月份的表里边了,其他分片表或许很少被查询,即数据歪斜了,有热门数据问题了。

咱们能够运用range规模+ hash哈希取模结合的分表战略,简略的做法便是:

在拆分库的时分,咱们能够先用range规模计划,比方订单id在0~4000万的区间,区分为订单库1;id在4000万~8000万的数据,区分到订单库2,将来要扩容时,id在8000万~1.2亿的数据,区分到订单库3。然后订单库内,再用hash取模的战略,把不同订单区分到不同的表。

面试必备:分库分表经典15连问

7.分库后,事务问题怎么处理

分库分表后,假定两个表在不同的数据库,那么本地事务现已无效啦,需求运用分布式事务了。

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

  • 两阶段提交
  • 三阶段提交
  • TCC
  • 本地消息表
  • 最大尽力告诉
  • saga

咱们能够看下这几篇文章:

  • 后端程序员必备:分布式事务根底篇
  • 看一遍就理解:分布式事务详解
  • 结构篇:分布式一致性处理计划

8. 跨节点Join相关问题

在单库未拆分表之前,咱们假如要运用join相关多张表操作的话,简直so easy啦。可是分库分表之后,两张表或许都不在同一个数据库中了,那么怎么跨库join操作呢?

跨库Join的几种处理思路:

  • 字段冗余:把需求相关的字段放入主表中,防止相关操作;比方订单表保存了卖家ID(sellerId),你把卖家名字sellerName也保存到订单表,这就不用去相关卖家表了。这是一种空间换时刻的思想。
  • 大局表:比方系统中一切模块都或许会依靠到的一些根底表(即大局表),在每个数据库中均保存一份。
  • 数据笼统同步:比方A库中的a表和B库中的b表有相关,能够守时将指定的表做同步,将数据调集聚集,生成新的表。一般能够凭借ETL东西。
  • 运用层代码组装:分开屡次查询,调用不同模块服务,获取到数据后,代码层进行字段核算组装。

9. order by,group by等聚合函数问题

跨节点的count,order by,group by以及聚合函数等问题,都是一类的问题,它们一般都需求根据全部数据调集进行核算。能够分别在各个节点上得到成果后,再在运用程序端进行合并。

10. 分库分表后的分页问题

  • 计划1(大局视界法):在各个数据库节点查到对应成果后,在代码端汇聚再分页。这样长处是事务无损,精准回来所需数据;缺陷则是会回来过多数据,增大网络传输,也会造成空查,

比方分库分表前,你是根据创立时刻排序,然后获取第2页数据。假如你是分了两个库,那你就能够每个库都根据时刻排序,然后都回来2页数据,然后把两个数据库查询回来的数据汇总,再根据创立时刻进行内存排序,最终再取第2页的数据。

  • 计划2(事务折衷法-禁止跳页查询):这种计划需求事务妥协一下,只要上一页和下一页,不答应跳页查询了。

这种计划,查询第一页时,是跟大局视界法相同的。可是下一页时,需求把当时最大的创立时刻传过来,然后每个节点,都查询大于创立时刻的一页数据,接着汇总,内存排序回来。

11. 分布式ID

数据库被切分后,不能再依靠数据库自身的主键生成机制啦,最简略能够考虑UUID,或者运用雪花算法生成分布式ID

雪花算法是一种生成分布式大局唯一ID的算法,生成的ID称为Snowflake IDs。这种算法由Twitter创立,并用于推文的ID。

一个Snowflake ID64位。

  • 1位:Java中long的最高位是符号位代表正负,正数是0,负数是1,一般生成ID都为正数,所以默以为0。
  • 接下来前41位是时刻戳,表明了自选定的时期以来的毫秒数。
  • 接下来的10位代表核算机ID,防止冲突。
  • 其他12位代表每台机器上生成ID的序列号,这答应在同一毫秒内创立多个Snowflake ID。

面试必备:分库分表经典15连问

12. 分库分表挑选哪种中间件

目前流行的分库分表中间件比较多:

  • Sharding-JDBC
  • cobar
  • Mycat
  • Atlas
  • TDDL(淘宝)
  • vitess

面试必备:分库分表经典15连问

咱们项目当时便是运用Sharding-JDBC完成的分库分表。

13.怎么评估分库数量

  • 对于MySQL来说的话,一般单库超越5千万记载,DB的压力就十分大了。所以分库数量多少,需求看单库处理记载才能有关。
  • 假如分库数量少,达不到涣散存储和减轻DB功能压力的意图;假如分库的数量多,对于跨多个库的拜访,运用程序需求拜访多个库。
  • 一般是主张分4~10个库,咱们公司的企业客户信息,就分了10个库。

14.笔直分库、水平分库、笔直分表、水平分表的区别

  • 水平分库:以字段为根据,依照必定战略(hash、range等),将一个库中的数据拆分到多个库中。
  • 水平分表:以字段为根据,依照必定战略(hash、range等),将一个表中的数据拆分到多个表中。
  • 笔直分库:以表为根据,依照事务归属不同,将不同的表拆分到不同的库中。
  • 笔直分表:以字段为根据,依照字段的活跃性,将表中字段拆到不同的表(主表和扩展表)中。

15.分表要停服嘛?不断服怎样做?

不用停服。不断服的时分,应该怎样做呢,主要分五个步骤:

  1. 编写署理层,加个开关(操控拜访新的DAO仍是老的DAO,或者是都拜访),灰度期间,仍是拜访老的DAO
  2. 发版全量后,敞开双写,既在旧表新增和修改,也在新表新增和修改。日志或者暂时表记下新表ID起始值,旧表中小于这个值的数据便是存量数据,这批数据便是要搬迁的。
  3. 经过脚本把旧表的存量数据写入新表。
  4. 停读旧表改读新表,此时新表现已承载了一切读写事务,可是这时分不要马上停写旧表,需求保持双写一段时刻。
  5. 当读写新表一段时刻之后,假如没有事务问题,就能够停写旧表啦