本文转载自微信公众号《是的'练级攻略》,作者是是的。转载本文请联系yes的练级指导公众号。大家好,我是。MySQL面试题又更新了!请继续接受新兵。说说分库分表?随着用户的激增和时间的积累,数据库中存在的数据越来越多。这时候数据库就会出现瓶颈、资源告警、查询慢等场景。首先,单机数据库所能承载的连接数、I/O、网络吞吐量都是有限的,所以当并发量增大时,数据库就会逐渐失效。此外,如果单个表的数据量太大,查询的性能也会下降。因为数据越多,B+树就越高,而树越高,查询I/O次数越多,性能越差。由于以上原因,不得已只能上分库分表。将之前存储在一个数据库实例中的数据拆分成多个数据库实例,部署在不同的服务器上,称为分库。将之前存储在一个表中的数据拆分成多个表,称为分表。一般来说:分表:就是为了解决单表数据量大导致查询慢的问题。大约需要拆分30到4000万行数据,但这仍然取决于每行数据的大小。有些字段很小,可能支持更多的行,而有些字段很大,可能无法承受1000万行。向上。分库:是为了解决服务器资源受单机限制,无法承受高并发访问的问题,将请求分发到多台服务器,减轻服务器压力。你们一般是怎么划分仓库的?一般按照业务划分仓库,比如订单仓库、用户数据库等。有时会针对一些特殊的库进行拆分,比如一些活动相关的库被拆分了。因为做活动的时候可能并发比较高,怕影响到现有的核心业务,所以即使有关联,也会单独拆分。那么你认为分库会带来哪些问题呢?首先是交易问题。我们使用关系数据库,很重要的一点是它保证了事务的完整性。分库之后,单机事务就不需要了,必须要用分布式事务来解决,而分布式事务基本是不完整的(我之前的文章总结了分布式事务,分布式事务的后台搜索已经起来了)。这是需要考虑的重要一点。当连表的JOIN问题在一个库时,我们也可以使用JOIN来查询连表,但是跨库之后就不能使用JOIN了。这时的解决办法是在业务代码中进行关联,即先查出一张表的数据,然后通过得到的结果查另一张表,再用代码进行关联,得到最终的结果。这种方法实现起来稍微复杂一些,但也可以接受。还有一些字段可以适当冗余。比如之前的表存储了一个关联的ID,但是业务往往需要返回对应的Name或者其他字段。这时候可以将这些字段冗余的加入到当前表中,去掉需要关联的操作。那么怎么分表呢?分表其实有两种:垂直分表horizo??ntaltable垂直分表,看图很直观:垂直分表就是把一些不常用的大字段剥离出来。像上面的例子:用户名是一个很常见的搜索结果,性别和年龄占用的空间不大,而地址和个人资料占用的空间比较大,我们都知道一个数据页的空间是有限的,拆分出来一些无用的数据,这样可以在一页上存储更多行的数据。内存存储了更多有用的数据,减少了磁盘访问次数,提高了性能。水平分表是因为一个表中的数据太多了。上面说了,数据越多,B+树越高,访问性能越差,所以进行水平拆分。其实抛开这些,简单的理解,是100条数据中找一条数据更快,还是10000条数据中找一条数据更快?就算有索引,也有很多厚厚的书目,翻目录很慢。表有什么问题?垂直分表还可以,但是需要联动,水平分表就有点麻烦了。排序、计数、分页问题如果一个用户的数据被拆分到多个表中,查询结果的分页就不像以前的单表那么直接了,计数操作也是一样的。只能通过业务代码实现或者使用中间件对每个表中的数据进行汇总、排序、分页和返回。像count这样的操作的结果其实是可以缓存起来的,然后每次新增或者删除数据的时候更新count。路由问题分表的路由可以分为:HashroutingrangeroutingroutingtableHashrouting,其实就是选择表中的某一列,然后进行Hash运算,然后取Hash的结果运算然后对分表个数取模,使得数据均匀分到不同的分表中。这和HashMap选择哪个bucket的原理是一样的。优点是数据分布均匀。缺点是添加子表时比较麻烦。想想HashMap的扩容,是不是要搬迁数据?这个分表也是一样的,我们都知道数据迁移是一件很麻烦的事情!范围路由其实很简单,可以是时间,也可以是地址,表示一定的范围。比如原来的User表,我可以把它分成User_HZ、User_BJ、User_SH,按照地名来划分Users。另一个例子是日志表。我可以把表分成log_202103和log_202104,按年月分日志。优点是比较容易扩展。例如,如果您现在想要一个GZ,请添加一个User_GZ。如果是五月,创建一个log_202105。缺点是可能数据分布不均,比如BJ的用户很多,或者某月有促销,日志量特别大等等。路由表是专门用来记录路由信息的表,看图就很清楚了。从图中我们可以知道,要在User_3用户表中查询UserID为2的用户数据。优点是灵活。如果要迁移数据,可以直接迁移,然后更改路由表。缺点是需要再检查一遍。每个查询都需要访问路由表,但这通常是缓存的。全局主键问题在单表的时候很简单,就是主键自增,但是现在分表后就有点尴尬了。所以需要一些手段来保证全局主键是唯一的。还是自增,但是设置了自增步长。比如现在有三张表,设置步长为3,三张表的ID初始值分别为1、2、3。这样,第一张表的ID增长为1、4、7,第二张表为2、5、8,第三张表为??3、6、9,这样就不会重复了。UUID,最简单,但是不连续的主键插入会导致页面分裂严重,性能不佳。分布式ID,比较出名的是Twitter开源的sonwflake雪花算法。具体我就不细说了,不然另写一篇。简单的使用redis来递增。如何设计上述路由问题的Sharding-Key?我们按照某一列对表进行分表,那一列就是Sharding-Key,查询时一定要包含进去。比如上面提到的log_202103,表示查询条件必须包含日期,这样才能找到正确的表。因此,设计时必须将查询条件考虑为Sharding-Key。举个经常被问到的Sharding-Key顺序表的例子。当你要查找一个订单时,你会通过订单号找到它,所以你应该使用订单ID作为Sharding-Key。但是想一想,当你打开外卖软件,想查找你的历史订单时,你没有订单ID,只有你的UserID,那么这时候你只能通过UserID遍历所有的子表,效率很低!所以你考虑使用UserID作为Sharding-Key!但是商家呢?商家肯定关心他今天卖出了多少订单,所以他也需要去查询订单,但是他只有自己的商家ID,所以如果要查询订单,只能遍历所有的子通过商户ID建表,效率很低!所以Sharding-Key不能满足所有的查询需求,只能曲线救国。一般的做法是冗余数据。将订单同步到另一张表,供商家使用。这个表根据商户ID作为Sharding-Key,数据也可以同步到ES。一般来说,这里的数据同步是异步处理的,不会影响正常流程。最后,今天的面试题主要是分库分表,基本涵盖了所有常见问题。MySQL面试题未完待续,持续更新~
