校招后端面经——数据库

校招后端面经--数据库

      • 缓存( redis)
        • 1. redis如何实现数据的持久化
            • RDB
            • AOF
        • 2. Redis的集群
            • 主从复制
            • 哨兵
            • 集群
        • 4. redis淘汰策略(内存回收)
        • 5. 线程安全问题
        • 6. 分布式锁
        • 7. key的访问和搜索
      • 数据库(MYSQL)
        • 1. Innodb引擎
            • 获取InnoDB行锁争用情况
            • InnoDB行锁类型。
        • 2. MyIASM引擎
        • 3. 两种引擎的区别
        • 4. 两种引擎的选择
        • 5. B树,B+树和红黑树
          • 1. B树
          • 2. B+树
          • 3. 红黑树
          • 4. 三者的区别
            • B树和红黑树
            • B+树和B树
        • 6. 数据库一致性的实现
          • 1. 传统关系型数据库
          • 2. 分布式数据库
          • paxos算法
          • Raft算法
            • 选举
            • 日志复制
            • 安全性
        • 7. SQL
          • 关键字执行顺序
        • 8. MYSQL原理
          • 1. 逻辑架构
          • 2. 查询过程
        • 9. 事务的实现
          • 1. 四个属性
          • 2. 原子性
          • 3. 持久性
          • 4. 隔离性
            • 并行事务的控制机制
          • 5. 一致性
        • 10. 数据库瓶颈
          • 1. 空间
            • 垂直切分

缓存( redis)

1. redis如何实现数据的持久化

两种方式:RDB和AOF

RDB

通过快照的方式,当符合一定条件时就会自动将内存中的所有数据生成一份副本存到磁盘中,根据配置文件,SAVE或BGSAVE命令。

配置文件:配置文件中设置了两个参数,一个是时间串口M,一个是改动的键的个数N,每当时间M内被改动的键的个数大于N时,就生成新的RDB文件替换原来的RDB文件存到磁盘。

SAVE:用户执行save命令同步执行快照时,在快照执行过程中会阻塞所有来自客户端的请求,导致redis长时间不响应。

BGSAVE:后台异步执行快照

过程:(1)redis使用fork函数复制一份当前进程(父进程)的副本(子进程)

​ (2)父进程继续接收并处理客户端发来的命令,而子进程开始将内存中的数据写入硬盘中的临时文件

​ (3)当子进程写入完所有数据后会用该临时文件替换旧的RDB文件

AOF

默认不开启,开启之后在每次执行命令后将命令本身记录下来。

当AOF记录冗余的命令时,会在配置文件中设置一定的参数,当达到当前参数的值时(比如文件大小超过了配置文件中设置的大小的值),重写AOF文件。

2. Redis的集群

主从复制
  1. 主数据库可以进行读写操作,从数据库保留主数据的副本,当主数据库写操作导致数据变化时,会自动把数据同步给从数据库,而从数据库一般是只读的。

  2. 过程:当一个从数据库启动后,会向主数据库发送SYNC的命令,主数据库收到连接命令后会开始在后台保存快照,并将保存期间接收到的命令缓存起来。当快照完成后,就会把快照文件和缓存命令发送给从数据库。从数据库收到之后就会载入快照文件并执行命令。这个过程也叫复制初始化。

  3. 若主从的连接断开重连:以前的版本是重新进行复制初始化,缺点是恢复过程的效率低下;现在的版本的一个改进是能够支持有条件的增量传输,重连后,主数据库只要将断连期间执行的命令发送给从数据库即可。

  4. 读写分离,适合读多写少的场景;当主数据库崩溃时,可以从从数据库恢复。

  5. 增量复制(重连使用):

    三个基础:(1)从数据库会存储主数据库的运行ID(每个redis运行实例都有自己唯一的ID)

​ (2)在复制同步阶段,主数据库每将一个命令传送给从数据库时,都会同时把该命令存放到一个

​ 积压队列中,并记录下当前积压队列中存放命令的偏移量的范围

​ (3)同时,从数据库接收到主数据库传来的命令时,会记录下该命令的偏移量

​ 过程:(1)首先主数据库会判断从数据库传过来的ID跟自己的ID是不是一样的,确保从数据库之前是和自己

​ 同步的。

​ (2)判断从数据库最后同步成功的命令的偏移量是否在积压队列中,如果在则可以执行增量复制,并

​ 将积压队列中相应的命令发送给从数据库

哨兵
  1. 作用:(1)监控主数据库和从数据库是否正常运行(哨兵之间可以相互监控)

    ​ (2)主数据库出现故障时自动将从数据库转换为主数据库

  2. 哨兵定时执行的3个操作:

    (1)每10s哨兵会向主数据库和从数据库发送INFO命令,可以获得当前数据库的相关信息从而实现新节点的

    ​ 自动发现

    (2)每2s哨兵会向主数据库和从数据库发送自己的信息(哨兵的地址,端口等)

    (3)每1s哨兵会向主数据库,从数据库和其他哨兵发送ping命令,监控数据库是否还在服务

  3. RAFT算法(选哨兵)

    作用:当监控到主数据库下线时,哨兵们会选举一个领头哨兵对主数据库进行故障恢复

    过程:(1)发现主数据库下线的哨兵节点(A)向每个哨兵节点发送命令,要求对方选自己成为领头哨兵

    ​ (2)如果目标哨兵没有选过其他人,则会统一将A设置成领头哨兵

    ​ (3)如果A发现又半数且超过quorm参数值的哨兵统一选择其成为领头哨兵,则A成功成为领头哨兵

    ​ (4)如果又多个哨兵节点同时参与选举,则会出现没有任何哨兵当选的可能。此时随机等待一个时

    ​ 间后重新选举

集群
  1. 原因:即使使用哨兵,redis集群的每个数据库依然存有集群中的所有数据,从而导致集群的总数据存储量受限于可用存储内存最小的数据库节点,形成木桶效应。

  2. 原理是:不同的节点管理不同的插槽,一个集群中共有16384个插槽

  3. 节点的增加:在集群中的任一节点(B)向新节点(A)发送CLUSTER MEET ip port的命令,新节点A接收到这个命令后,会与B进行握手,使B把A当作当前集群中的一员。握手成功后,B会使用Gossip协议将节点A的消息通知给集群中的每一个节点。

  4. 插槽分配:

    新的节点加入集群后,要么复制每个主数据库来以从数据库的形式运行,要么向集群申请分配插槽来以主数据库的形式运行

    在一个集群中,所有的键会被分配给16384的插槽,而每个主数据库会负责处理其中的一部分插槽。

    两种情况:

    (1)插槽之前没有被分配过,现在想分配给指定节点

    ​ 使用CLUSTER ADD SLOT S的命令实现

    (2)插槽之前被分配过,现在想移动到指定节点

    ​ 若槽中无数据,可用CLUSTER SETSLOT 插槽号 NODE 新节点的运行ID,若槽中有数据,数据是不会被

    ​ 迁移的,会导致数据的丢失。因此还要把数据迁移过去

    迁移过程发生客户端的访问的处理:0号插槽从A迁移到B的过程中,当客户端向A请求插槽0中的键时,如果键存在,正常返回,否则A就会返回一个move的重定向请求,客户端接收这个请求后再向B发送请求。

  5. 获取与插槽对应的节点:

    (1)每个键键名的有效部分使用CRC16算法计算哈希值,然后取对16384的余数,分配到16384个插槽

    (2)使用-c参数来实现自动重定向

    (3)请求重定向会影响性能,优化方法是设置缓存,缓存插槽的路由信息,先到缓存里面找插槽所在的节点

  6. 故障查找

    (1)每个节点会定期向其他节点发送ping命令,来判断是否有节点下线

    (2)一旦A节点认为B疑似下线,会在集群中传播这个消息,所有其他节点收到消息后会记录这一信息

    (3)当C收集到半数以上的节点认为B是疑似下线,就会把B设置为下线,并通知其他节点,让B在整个集群

    ​ 中下线

    若下线的是主数据库,且存在从数据库,对从数据库使用raft算法选一个从数据库转为主数据库

4. redis淘汰策略(内存回收)

LRU算法(Redis的并发竞争问题)

5. 线程安全问题

内部实现采用epoll

Redis为单进程单线程模式,采用队列模式将并发访问变为串行访问。Redis本身没有锁的概念,Redis对于多个客户端连接并不存在竞争

采用了线程封闭的观念,把任务封闭在一个线程,自然避免了线程安全问题,不过对于需要依赖多个redis操作的复合操作来说,依然需要锁,而且有可能是分布式锁。

6. 分布式锁

先拿setnx来争抢锁,抢到之后,再用expire给锁加一个过期时间防止锁忘记了释放 。因为把setnx和expire合成一条指令来用的 ,所以如果在setnx之后执行expire之前进程意外crash或者要重启维护的时候,不会造成锁无法释放。

7. key的访问和搜索

  1. 假如Redis里面有1亿个key,其中有10w个key是以某个固定的已知的前缀开头的,如果将它们全部找出来?

    使用keys指令可以扫出指定模式的key列表。

  2. 如果这个redis正在给线上的业务提供服务,那使用keys指令会有什么问题?

    redis的单线程的。keys指令会导致线程阻塞一段时间,线上服务会停顿,直到指令执行完毕,服务才能恢复。这个时候可以使用scan指令,scan指令可以无阻塞的提取出指定模式的key列表,但是会有一定的重复概率,在客户端做一次去重就可以了,但是整体所花费的时间会比直接用keys指令长。

数据库(MYSQL)

1. Innodb引擎

提供了对数据库ACID(原子性,一致性,隔离性,持久性)事务支持,提供了行级锁和外键约束,他的设计目标是处理大容量的数据库系统。MYSQL执行Innodb会在内存中建立缓冲池,用于缓冲数据和索引。但没有保存表的行数,当要获取表的行数时要扫描全表。因为支持行级锁,锁的粒度比较小,写操作不会锁定全表,所以在并

发较高的情况时,使用这个引擎会提升效率。

获取InnoDB行锁争用情况
1. show status like 'innodb_row_lock%'
2. Show engine innodb status\G;
InnoDB行锁类型。

共享锁(S):允许一个事务去读一行,阻止其他事务获得相同数据集的排他锁。
排他锁(X):允许获得排他锁的事务更新数据,阻止其他事务取得相同数据集的共享读锁和排他写锁。

另外,为了允许行锁和表锁共存,实现多粒度锁机制,InnoDB还有两种内部使用的意向锁(Intention Locks),这两种意向锁都是表锁。

意向共享锁(IS):事务打算给数据行加行共享锁,事务在给一个数据行加共享锁前必须先取得该表的IS锁。
意向排他锁(IX):事务打算给数据行加行排他锁,事务在给一个数据行加排他锁前必须先取得该表的IX锁。

2. MyIASM引擎

该引擎是MYSQL的默认引擎,没有提供对数据库事务的支持,也不支持行级锁和外键,因此写操作需要锁定整个表,效率较低。该引擎存储了表的行数,当需要表的行数,不需要扫描全表,可以直接获取。当读操作远远多于写操作时,用这个引擎就很不错。

3. 两种引擎的区别

两个引擎的索引都是用B+树的数据结构,主要有两个区别

  1. MYIASM分为两个文件,一个是数据文件,一个是索引文件,检索的时候先搜索索引文件,获得对应的数据的地址,然后根据地址在数据文件中取数据;

    Innodb的数据文件本身就是索引文件,一般是主键作为索引的key值,检索时根据主键找到对应的数据

  2. MYIASM的索引文件的数据域存储的是真实数据在数据文件中的地址;

    Innodb也可以用辅助索引文件,只是该索引文件的数据域存放的是数据的主键,当在辅助索引文件中找到主键后,通过主键在数据文件中找到真实数据。因此需要检索两遍

4. 两种引擎的选择

大量的数据存储趋向于选择Innod,因为它支持事务处理和故障恢复。Innob可以利用事务日志进行数据恢复

5. B树,B+树和红黑树

1. B树

根节点至少有两个子节点

每个节点最多有M-1关键值和M个子节点

除根节点外其他节点至少有M/2个子节点

2. B+树

有k个子节点必然有k个关键关键值

非叶子节点仅具有索引的作用,跟记录有关的信息都放在叶子节点

树的所有叶子节点构成一个有序链表

3. 红黑树

每个节点或者是黑色,或者是红色

根节点是黑色

每个叶子节点(NIL)是黑色[注意:这里叶子节点,是指为空(NIL或NULL)的叶子节点!]

如果一个节点是红色的,则它的子节点必须是黑色的

从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点

4. 三者的区别
B树和红黑树

红黑树每个节点只有左右两个子节点,树的深度过大,造成磁盘IO读写过于频繁,进而导致效率低下 ,而B树可以有多个子女,减少树的高度,极大提高查询效率

B+树和B树
  1. B+的内部结点并没有指向关键字具体信息的指针。因此其内部结点相对B树更小。如果把所有同一内部结点的关键字存放在同一盘块中,那么盘块所能容纳的关键字数量也越多。一次性读入内存中的需要查找的关键字也就越多。相对来说IO读写次数也就降低了。
  2. 由于非叶子结点并不是最终指向文件内容的结点,而只是叶子结点中关键字的索引。所以任何关键字的查找必须走一条从根结点到叶子结点的路。所有关键字查询的路径长度相同,导致每一个数据的查询效率相当。
  3. B树必须用中序遍历的方法按序扫库,而B+树直接从叶子结点挨个扫一遍就完了,B+树支持range-query非常方便,而B树不支持。这是数据库选用B+树的最主要原因。(B+树的遍历更加高效,B树需要以中序的方式遍历节点,而B+树只需把所有叶子节点串成链表就可以从头到尾遍历)。

6. 数据库一致性的实现

1. 传统关系型数据库

往往通过硬件技术支持,比如事务日志回滚,因此不会存储多份来保证

2. 分布式数据库

使用paxos和Raft

paxos算法

见上面的分布式数据一致性

Raft算法

三个步骤:选举领头数据库,日志复制,安全保证

Raft算法中,对节点的状态分为3种角色,分别是Leader,Follower和Candidate

Leader:负责处理来自客户端的请求,负责将日志同步到Follower中,并收集Follower发送过来的心跳

Follower:当集群刚刚启动时,所有节点均为Follower状态,它的主要工作是响应Leader的日志同步请求,响应Candidate的请求,以及把请求到Follower中的事务请求转发给leader

Candidate:选举leader时负责投票,选举出leader后,节点将candidate状态变为leader状态

Raft把时间划分为一个个时间段(term),每个时间段有以下原则:

  1. 每个时间段,最多只有一个leader
  2. 某些时间段,有可能存在选举失败,没有leader的情况
  3. 每个时间段都是一个连续递增的编号term
  4. 若果Follower的term编号比其他Follower的term编号小时,将该Follower的term编号将更新term编号,以保持与其他Follower的term编号一致
选举

Raft的选举定时器触发之后,每个节点的触发时间都不相同

所有节点开始状态为Follower,当定时器触发选举后该节点Term编号递增,该节点的状态由Follower转为Candidate,并向其他节点发起投票请求。选举由三种情况:

  1. 发起投票请求的节点收到超过半数的节点的投票,该节点变为Leader,并通知其他节点
  2. 如果收到投票请求后,该节点发现发起投票的节点的term大于自己,则该节点状态从candidate转为follower,否则保持candidate状态,并且拒绝该投票
  3. 选举期间发生超时,则term编号递增,重新发起选举
日志复制

当Leader选出来后,所有的事务操作必须要经过Leader的处理。这些事务操作成功之后,会被按顺序写入日志中,此时不提交。

然后leader发送请求将日志同步到follower中,follower返回ack响应。当leader接到超过半数的follower的ack响应后,提交事务。

安全性

当某个follower在同步日志时发生了失败,但未来该follower又可能被选举为leader时,就有可能导致前一个leader已经提交的日志发生覆盖,这样就导致节点执行不同序列的日志

安全性体现在选举出来的leader一定包含之前已经提交的日志机制,主要遵循的原则如下:

  1. 每个Term只能选举一个leader
  2. 当candidate重新选举leader时,新的leader必须要包含之前已经提交的日志
  3. candidate在选举新的leader时,使用term来保证日志的完整性

7. SQL

HAVING语句通常与GROUP BY语句联合使用,用来过滤由GROUP BY语句返回的记录集

HAVING语句的存在弥补了WHERE关键字不能与聚合函数联合使用的不足。

关键字执行顺序

Where,Group By,Having,Order By的执行顺序如下:首先Where将原始记录中不满足条件的记录删除(所以应该在where中尽量将不满足条件的记录筛掉,这样可以减少分组),然后通过Group By关键字后面指定的分组条件将筛选得到的试图进行分组,接着系统根据Havig关键字后面指定的筛选条件,将分组视图后不满足条件的记录筛掉,然后按照Order By语句对试图进行排序,这样最终结果就产生了。

8. MYSQL原理

1. 逻辑架构

img

  1. 最上层为客户端,连接处理,授权认证,安全等都在这一层
  2. 核心服务均在中间层,包括查询解析,分析,优化,缓存等;所有跨存储引擎的功能也在这一层实现,如存储过程,触发器,视图等
  3. 最下层为存储引擎,其负责MYSQL中的数据存储和提取,每种引擎都有自己的优势和劣势。中间层通过API与存储引擎通信,这些API接口屏蔽了不同存储引擎之间的差异
2. 查询过程

建议看这个博客 很全面

校招后端面经——数据库_第1张图片

  1. 客户端向MYSQL服务器发送一条查询请求
  2. 服务器首先检查查询缓存,如果命中缓存,则立刻返回存储在缓存中的结果。否则进入下一阶段
  3. 服务器进行SQL解析和预处理,再由优化器生成对应的执行计划
  4. MYSQL根据执行计划,调用存储引擎的API来执行查询
  5. 将结果返回给客户端,同时缓存查询结果

9. 事务的实现

1. 四个属性

Atomicity:原子性

Consistency:持久性

Isolation:隔离性

Durability:一致性

2. 原子性

保证原子性,就是在异常发生时,对已经执行的操作进行回滚。Mysql中的恢复机制是用回滚日志(undo log),所有事务的修改都会先记录到回滚日志当中,然后再对数据库进行写入。当错误发生时,数据库会按照日志逻辑地撤销之前的修改。

3. 持久性

一旦事务提交,那么数据就会被写入数据库持久保存起来,无法再次回滚。通过重做日志来实现的(redo log)。重做日志由两部分组成:一是内存中的重做日志缓冲区,这是易失的,二是磁盘上的重做日志文件,这是持久的。

当我们尝试对数据进行修时,它先将数据从磁盘取到内存,并更新内存中缓存的数据,然后生成一条重做日志写入重做日志缓存中。当事务提交时,会先把重做日志缓存刷新到重做日志文件中,再将数据修改到数据库中。如果事务提交后还没来得及写入数据库就宕机了,可以从重做日志中恢复,保证了数据的持久性。

4. 隔离性

用来解决并行事务带来的问题,通过锁或者时间戳来实现。四种数据库的事务的隔离级别:READ UNCOMMITEDREAD COMMITEDREPEATABLE READSERIALIZABLE;每个事务的隔离级别其实都比上一级多解决了一个问题 ,一级比一级更加严格

  1. RAED UNCOMMITED (未提交读,脏读):使用查询语句不会加锁,可能会读到未提交的行 (脏读:在一个事务中,读取了其他事务未提交的数据。)
  2. READ COMMITED (已提交读):只对记录加记录锁,而不会在记录之间加间隙锁,所以允许新的记录插入到被锁定记录的附近,所以再多次使用查询语句时,可能得到不同的结果 (不可重复读:在一个事务中,同一行记录被访问了两次却得到了不同的结果。)
  3. REPEATABLE READ (可重复读):多次读取同一范围的数据会返回第一次查询的快照,不会返回不同的数据行,但是可能发生幻读 (幻读:在一个事务中,同一个范围内的记录被读取时,其他事务向这个范围添加了新的记录,但再次查询结果中并没有这条记录,而且无法再次插入同样的数据。)
  4. SERIALIZABLE (可序列化):InnoDB 隐式地将全部的查询语句加上共享锁
并行事务的控制机制

乐观锁和悲观锁其实都是并发控制的机制,同时它们在原理上就有着本质的差别;

  • 乐观锁是一种思想,它其实并不是一种真正的『锁』,它会先尝试对资源进行修改,在写回时判断资源是否进行了改变,如果没有发生改变就会写回,否则就会进行重试,在整个的执行过程中其实都没有对数据库进行加锁;
  • 悲观锁就是一种真正的锁了,它会在获取资源前对资源进行加锁,确保同一时刻只有有限的线程能够访问该资源,其他想要尝试获取资源的操作都会进入等待状态,直到该线程完成了对资源的操作并且释放了锁后,其他线程才能重新操作资源;
5. 一致性

CAP 定理中的数据一致性,分布式系统中的各个节点中对于同一数据的拷贝有着相同的值;

ACID 中的一致性是指数据库的规则,如果 schema 中规定了一个值必须是唯一的,那么一致的系统必须确保在所有的操作中,该值都是唯一的

10. 数据库瓶颈

1. 空间

根据下面的两个博客整理的

https://cloud.tencent.com/developer/article/1328292

https://www.cnblogs.com/butterfly100/p/9034281.html

进行分库分表,数据切分根据其切分类型,可以分为两种方式:垂直(纵向)切分和水平(横向)切分 。

垂直切分
  1. 垂直分库

    垂直分库就是根据业务耦合性,将关联度低的不同表存储在不同的数据库。做法与大系统拆分为多个小系统类似,按业务分类进行独立划分。与"微服务治理"的做法相似,每个微服务使用单独的一个数据库。如图:

    校招后端面经——数据库_第2张图片

  2. 垂直分表

    垂直分表是基于数据库中的"列"进行,某个表字段较多,可以新建一张扩展表,将不经常用或字段长度较大的字段拆分出去到扩展表中。在字段很多的情况下(例如一个大表有100多个字段),通过"大表拆小表",更便于开发与维护,也能避免跨页问题,MySQL底层是通过数据页存储的,一条记录占用空间过大会导致跨页,造成额外的性能开销。另外数据库以行为单位将数据加载到内存中,这样表中字段长度较短且访问频率较高,内存能加载更多的数据,命中率更高,减少了磁盘IO,从而提升了数据库性能。

  3. 优点:

    • 解决业务系统层面的耦合,业务清晰
    • 与微服务的治理类似,也能对不同业务的数据进行分级管理、维护、监控、扩展等
    • 高并发场景下,垂直切分一定程度的提升IO、数据库连接数、单机硬件资源的瓶颈
  4. 缺点

    • 部分表无法join,只能通过接口聚合方式解决,提升了开发的复杂度
    • 分布式事务处理复杂
    • 依然存在单表数据量过大的问题(需要水平切分)

你可能感兴趣的:(面经)