数据库面试题:分库分表相关问题

分库分表

分库分表即将表中的数据分为多个部分,分别放在不同的数据库中。所以理解分库分表之前我们先理解分表。

一.分表

1.1 为什么要分表

日常开发中我们经常会遇到大表的情况,所谓的大表是指存储了百万级乃至千万级条记录的表。这样的表过于庞大,导致数据库在查询和插入的时候耗时太长,性能低下,如果涉及联合查询的情况,性能会更加糟糕。分表的目的就是减少数据库的负担,提高数据库的效率,通常点来讲就是提高表的增删改查效率。

1.2分表的方法

分表的方法分为纵向分表和横向分表

1.2.1 纵向分表

一般来说,表中每一行的数据都属于某一个“事物”。即一行中每一列代表某个“事物”的不同属性。例如学生表而言:学号,姓名,班级,性别,电话。

纵向分表方法:

  • 将一个“事物”的不同属性划分为不同的表,即按列划分。
  • 将本来可以在同一个表的内容,人为划分为多个表。(所谓的本来,是指按照关系型数据库的第三范式要求,是应该在同一个表的。)

分表理由:根据数据的活跃度进行分离,(因为不同活跃的数据,处理方式是不同的)

案例:

对于一个博客系统,文章标题,作者,分类,创建时间等,是变化频率慢,查询次数多,而且最好有很好的实时性的数据,我们把它叫做冷数据。而博客的浏览量,回复数等,类似的统计信息,或者别的变化频率比较高的数据,我们把它叫做活跃数据。所以,在进行数据库结构设计的时候,就应该考虑分表,首先是纵向分表的处理。

纵向分表后的优势:

  • 首先存储引擎的使用不同,冷数据使用MyIsam 可以有更好的查询数据。活跃数据,可以使用Innodb ,可以有更好的更新速度。
  • 其次,对冷数据进行更多的从库配置,因为更多的操作时查询,这样来加快查询速度。对热数据,可以相对有更多的主库的横向分表处理。
    其实,对于一些特殊的活跃数据,也可以考虑使用memcache ,redis之类的缓存,等累计到一定量再去更新数据库。或者mongodb 一类的nosql 数据库,这里只是举例,就先不说这个。

1.2.2 横向分表

顾名思义,横向分表就是把大的表结构,横向切割为同样结构的不同表,如,用户信息表,user_1,user_2 等。表结构是完全一样,但是,根据某些特定的规则来划分的表,如根据用户ID来取模划分。

分表理由:根据数据量的规模来划分,保证单表的容量不会太大,从而来保证单表的查询等处理能力。

案例:

同上面的例子,博客系统。当博客的量达到很大时候,就应该采取横向分割来降低每个单表的压力,来提升性能。例如博客的冷数据表,假如分为100个表,当同时有100万个用户在浏览时,如果是单表的话,会进行100万次请求,而现在分表后,就可能是每个表进行1万个数据的请求(因为,不可能绝对的平均,只是假设),这样压力就降低了很多很多。

二. 分库

  • 分完表之后其实如果不进行分库的话也是可以解决之前单表查询耗时的问题。
  • 分库主要解决的单台服务器数据库高并发问题。

针对分库,大家可以去了解一下MYSQL的主从同步,以及一些服务器集群的分库算法(例如:一致性哈希 https://blog.csdn.net/weixin_44844089/article/details/115665900

三. 分库分表相关问题

3.1 分库分表如何保证唯一ID

在我们进行分库分表后,我们对每一行数据都应该有一个全局的唯一标识ID,如果我们之前用的是自增主键的话,这样就没办法让多个划分之后的表保证每一行都有一个全局唯一ID。那么怎么办呢?

3.1.1 UUID(不推荐)

我们第一个想到的方案就是不使用自增主键,直接对每一行生成一个UUID即可。这种方法的确是可以的,但是实际上来说,主键的有序性往往能够有利于数据的存储和写入性能。

其次,UUID不具备业务相关性。

我们现在开发的项目都是依据公司业务开展的,而我们的唯一ID一般都是和业务有关系的,比如,有些订单ID中带上了时间的维度、机房的维度以及业务类型等维度。也就是为了我方便进行定位是那种业务的订单,才会这么设计的,是不是。

递增主键的优势:

  • 某些情境下可以减少数据存储。例如用户关注航班这个模块来说,我们查看某个航班关注用户按照时间的先后进行排序。因为现在的ID是时间上有序的,所以现在我们就可以按照ID来进行排序了,同时这样对于有些并不是要存储时间的业务来说,会减少不少的存储空间。
  • 有利于数据插入性能。我们知道主键其实在数据库中就是一种索引,而索引在MySql数据库的B+数据结构中是顺序存储的,所以每次插入的时候就是递增排序的,直接追加到后面就行。如果是无序的话,则每次插入数据之前还得查找它应该所在的位置,这无疑就会增加数据的异动等相关的开销。

UUID是由32位的16进制数字组成的字符串,不仅在存储空间上造成浪费,更不具备我们业务相关性。那我们该怎么解决呢?其实twitter提出来的Snowflake 算法就能很好满足我们现在的要求,满足了主键ID的全局唯一性、单调递增性,也可以满足我们的业务相关。所以,我们现在使用的唯一ID生成方式就是使用Snowflake算法,这个算法其实很简单。下面我们来对其进行讲解,并对其相应改造使其能用到我们的开发业务中来。

3.2 Snowflake算法

Snowflake 是由 64 比特bit二进制数字组成的,一共分为4大部分:

  • 1位默认不使用
  • 41位时间戳
  • 10位机器ID
  • 12位序列号

在这里插入图片描述

  1. 我们从上图中可以看出snowflake算法的第二部分的41位时间戳,大概可以支撑2^41/1000/60/60/24/365 年,也就是大约有69年。我们设计一个系统用69年应该是足够了吧。
  2. 10位的机器ID我们可以怎么使用呢?我们可以划分成大概2到3位IDC,也就是可以支撑4到8个IDC机房;然后划分7到 8 位的机器ID,即可以支撑128~256台机器。
  3. 12位的序号,就代表每个节点每毫秒可以生成4096个ID序号。

如何改造

我们现在已经知道了Snowflake 算法的核心原理,并且知道了其有64位的二进制数据,那我们就可以根据自己业务进行改造以更好的来为我们业务服务。一般不同的公司对其进行改造的方式都不尽相同,但是道理都是一样的。我们可以这么做:

  • 减少序列号的位数,增加机器ID的位数,是为了用来支撑我们单IDC的更多机器。
  • 将我们业务ID加入进去用来区分我们不同的业务。比如,1位0 + 41位时间戳 + 6位IDC(64个IDC) + 6位业务信息(支撑64种业务) + 10位自增序列(每毫秒1024个ID)

在这里插入图片描述
如此,我们就可以在单机房部署这么一个统一ID发号器,然后用Keeplive 保证高可用(对于高可用不熟悉的回去看看哈「高可用」你们服务器挂了怎么办,我们是这样做的)可以将不同的业务模块ID加入进去,这样的好处是即使哪个业务出问题了,我只看ID号我就分析出来,比如,我看到现在ID号有我的订单ID业务,我就去看订单模块。

开发如何使用

现在我们知道Snowflake 算法原理了,还知道了我们可以进行改造了。那我们开发人员该怎么去使用,来为我们业务生成统一的唯一ID呢?

1,直接嵌入到业务代码

嵌入业务代码的意思就是,这个snowflake算法就部署在和我们业务相同的服务器上,这样我们代码使用的时候,就不用了跨网络调用,性能相对比较好。但是也是有缺点的,因为我们的业务机器肯定是很多的,这就意味着我们发号器算法需要更多的机器ID位数。同时,太多的业务服务器我们会很难保证业务机器id的唯一性,这里就需要引用zookeeper一致性组件来保证每次机器重启都能能获得唯一的机器ID。

2,独立部署成发号器服务

也就是说,我们将其作为单独的服务部署到单独的机器上,已对外提供服务。这样就是多了网络的传输,不过影响不大,比如,我可以将其部署成一个主备的方式对外提供发号服务,机器ID可以用作序列号使用,这样也就是会有更多的自增序号,有部分大厂就是以这样单独的服务提供出来的。

开发中避坑大法

1,虽然snowflake很优秀,但是它是基于系统时间的,万一我们系统的时间不准怎么办,就会造成我们的ID会重复。那我们的做法就是,要利用系统的对时功能,一旦发现时间不一致,就暂停发号器,等到时钟准了在启用。

2,还有一个坑比较关键,也是常发生的,就是当我们的QPS并发不高的时候,比如每毫秒只生成一个ID号,这样就是直接结果是,每次生成的ID末尾都是1,这样我们分库分表就会出现问题呀对吧,因为我们用这个ID去分库分表呀,会造成数据不均匀,是吧,忘记了去复习哈(数据库分库分表方案,优化大量并发写入所带来的性能问题)那我们怎么解决呢?

我们可以将时间戳记录从毫秒记录改为秒记录,这样我一秒可以发好多个号了

生成的序列号起始号随机启动,比如这一秒起始号是10,我下一秒随机了变成了28,这样就更加分散开了。

总结,今天我们针对分库分表之后带来的第一个直接影响我们开发的问题,就是主键ID唯一性的问题,然后说到了使用Snowflake算法去解决,并且对其原理和使用进行了详细的讲解,同时,还将其在使用中遇到的坑给讲出来了,也对其进行了填坑分析,让大家直接避免遇到同样的问题。当然生成唯一ID有多种,我们根据业务选择合适我们自己的就好,你们是基于什么方式生成的可以也可以告诉大家。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值