MySQL 八股文

有的面试官挺喜欢用自己那套规则来面试,搞的乌烟瘴气,全是背题的。

Mysql 存储引擎

创建表格的时候指定

名称 名称解释 事务
MyIsam 非聚集索引,索引和数据分别存放 非事务安全型 支持表锁
InnoDB 数据和索引在一个文件中 事务安全型 支持行锁

MyISAM

特点:需要访问速度快,对事务完整性没有要求,以SELECT、INSERT为主的应用基本都可以使用这个引擎

  • 不支持事务

  • 不支持外键

  • 访问速度快

  • 损坏和修复:每一个表都有一个标志,来标注上次是否正常关闭,MyISAM类型的表可能会损坏,可以使用CHECK TABLE语句来检查MyISAM表的健康,并用REPAIR TABLE语句修复一个损坏的MyISAM表。

MyISAM的表还支持3种不同的存储格式:

  • 静态(固定长度)表:默认的存储格式。静态表中的字段都是非变长字段,这样每个记录都是固定长度的,这种存储方式的优点是存储非常迅速,容易缓存,出现故障容易恢复;缺点是占用的空间通常比动态表多。
  • 动态表:动态表包含变长字段,记录不是固定长度的,这样存储的优点是占用空间较少,但是频繁到更新删除记录会产生碎片,需要定期执行OPTIMIZE TABLE语句或myisamchk -r命令来改善性能,并且出现故障的时候恢复相对比较困难。
  • 压缩表:压缩表由myisamchk工具创建,占据非常小的空间,因为每条记录都是被单独压缩的,所以只有非常小的访问开支。一般而言,如果你的系统是I/O瓶颈,那么可以使用CPU进行压缩与解压缩,以CPU换取I/O。

使用场景:

  1. 做很多 count 的计算
  2. 插入不频繁,查询非常频繁
  3. 没有事务

InnoDB

特点: InnoDB存储引擎提供了具有提交、回滚和崩溃恢复能力的事务安全。但是对比MyISAM的存储引擎,InnoDB写的处理效率差一些并且会占用更多的磁盘空间以保留数据和索引。

使用场景

  1. 可靠性要求比较高,或者要求事务
  2. 表更新和查询都相当的频繁,并且表锁定的机会比较大的情况

为什么使用 B+ 树

相对于二叉树: 理论上二叉树的查找比较次数都是最小最划算的,但是为什么不用二叉树呢?

因为我们要考虑磁盘IO的影响,索引存放在磁盘中,不可能全部加到内存中,只能逐一加载每一个磁盘页,所以我们要减少IO次数,而可以相当程度上认为IO次数就是树高度,

相对于 B 树: B+ 树的中间节点不保存数据,所以磁盘页能容纳更多节点元素,更”矮胖“;如果是范围查找,B树需要反复的查找,而 B+ 树只需要遍历即可。

Mysql 数据库索引

这块八股文都比较复杂,还得专门找时间学习

数据库的事务特性

说道事务特性,还得说说四大特性:

  • 原子性(Atomicity):要么成功,要么失败回滚,失败的操作不允许对数据库造成任何影响
  • 一致性(Consistency):事务必须从一个状态转换到另一个状态,比如转账,A->B后,两个人的钱加起来总和不变
  • 隔离性(Isolation):隔离性是数据库为每个用户开启的事务,不能被其他事务干扰,如果两个并发的事务,T1和T2,相互感受不到对方对数据库的修改对自己的影响。
  • 持久性(Durability):一旦事务提交了,返回结果了,对数据库的修改就是永久的。

如果事务不隔离,就会出现以下几种问题:

  1. 脏读:某个事务已更新一份数据,另一个事务在此时读取了同一份数据,由于某些原因,前一个RollBack了操作,则后一个事务所读取的数据就会是不正确的。
  2. 不可重复读:在一个事务的两次查询之中数据不一致,这可能是两次查询过程中间插入了一个事务更新的原有的数据。
  3. 幻读:在一个事务的两次查询中数据笔数不一致,例如有一个事务查询了几列(Row)数据,而另一个事务却在此时插入了新的几行数据,先前的事务在接下来的查询中,就会发现有几列数据是它先前所没有的。

数据库的四种事物的隔离级别

  1. Read Uncommitted(读取未提交内容):在该隔离级别,所有事务都可以看到其他未提交事务的执行结果。本隔离级别很少用于实际应用,因为它的性能也不比其他级别好多少。读取未提交的数据,也被称之为脏读(Dirty Read)

  2. Read Committed(读取已提交 ):这是大多数数据库系统的默认隔离级别(但不是MySQL默认的)。它满足了隔离的简单定义:一个事务只能看见已经提交事务所做的改变。这种隔离级别也支持所谓的不可重复读(Nonrepeatable Read),因为同一事务的其他实例在该实例处理其间可能会有新的commit,所以同一select可能返回不同结果

  3. Repeatable Read(可重读):这是MySQL的默认事务隔离级别,它确保同一事务的多个实例在并发读取数据时,会看到同样的数据行。不过理论上,这会导致另一个棘手的问题:幻读 (Phantom Read)。简单的说,幻读指当用户读取某一范围的数据行时,另一个事务又在该范围内插入了新行,当用户再读取该范围的数据行时,会发现有新的“幻影” 行。InnoDB和Falcon存储引擎通过多版本并发控制(MVCC,Multiversion Concurrency Control)机制解决了该问题

  4. Serializable(可串行化):这是最高的隔离级别,它通过强制事务排序,使之不可能相互冲突,从而解决幻读问题。简言之,它是在每个读的数据行上加上共享锁。在这个级别,可能导致大量的超时现象和锁竞争。

隔离性如何实现

有两个重要的办法,就是锁和MVCC

锁🔐

读写锁:很常见不解释了

两段式提交锁:

第一个阶段只能加锁、或者释放锁 第二个阶段只会释放锁

具体需要很多其他的针对四种不同隔离级别具体分析TODO:

MVCC

在InnoDB在读已提交和可重复读下工作的一种机制,

在事务操作的过程中会生成版本链,每一次修改都会在版本链条中记录,SELECT操作则会去版本链中获取记录,这就实现了读-写,写-读的并发执行,提升了系统的性能

因为加锁的方式会产生死锁问题。

专门开了一篇文章 mysql-mvcc

Mysql Explain

explain 一条 select 语句可以帮助你判断这次查询是否命中索引,是怎么样的扫描方式。

eq_ref > ref > range > index > ALL

  • eq_ref: 主键索引(primary key)或者非空唯一索引(unique not null)等值扫描
  • ref: 非主键非唯一索引等值扫描
  • range:范围扫描
  • index:索引树扫描
  • ALL:全表扫描(full table scan)

行锁还是表锁

如果命中了索引,就可以只锁该行,如果没有命中或者,索引对应的行太多,就会锁整个表。

选择了 B+ 树而不是 B 树,AVL树 和 红黑树

  1. 磁盘读写速度(考虑到磁盘读写速度)
  2. 磁盘不希望产生随机IO情况出现

一定要加锁么?不用锁如何实现并发

多版本并发控制MVCC?

MySQL 如何保证 crash-safe

两阶段提交 + redo log 保证了 crash-safe

Linux 操作系统在实现向磁盘写入的时候,是先把数据写进内核中,然后再调用 fsync() 函数刷盘,通过参数配置innodb_flush_log_at_trx_commit 即可决定数据的稳定性,0代表仅仅写入用户态buffer,1代表写到硬盘中,2代表写到内核态 cache page 中

Buffer Pool

  • Buffer Pool 是一块内存空间。 将页面加载到Buffer Pool 中可以减少磁盘IO 提高查询效率。
  • 每次更新先更新Buffer Pool 中的记录,并将存放该记录的页标记为 脏页。后台进程每隔一段时间刷新脏页到磁盘提高磁盘IO效率。Buffer Pool的引入提升了读写效率。
  • 缓存命中率 体现了Buffer Pool的利用效率。Buffer Pool 通过LRU算法来淘汰页面提高缓存命中率。

redo log

如果数据写入了 Buffer Pool,会紧接着写进 redo log 中,如果 Buffer Pool 中的数据来不及刷入磁盘,数据库重启之时,就可以从 redo log中读取修改的内容,重新做一遍同样的操作。

redo log 能保证信息在崩溃的时候不丢失,因为他们是存在磁盘里的。

  • innodb_log_group_home_dir 指定redo日志文件存放的目录。
  • innodb_log_file_size 指定每个redo日志文件的大小。
  • innodb_log_files_in_group 指定redo日志的个数

其实每次都写磁盘会很难受,磁盘跟不上,因此一般是按组写入,那么又势必会引入一个 redo log buffer,并且强调磁盘顺序写入。但是又如何保证崩溃的时候,不丢失呢?

照搬 Buffer Pool 的原理,来看看 redo log 的刷盘时机:

  • redo log buffer 空间不足的时候。
  • 事务提交的时候
  • 后台线程在不停的刷
  • 服务正常关闭的时候
  • checkpoint的时候

从 flush 磁盘的时机来看,redo log buffer 的更为频繁,可以说 提交事务的时候,保证事务相关的redo log 已经存储了。

有个变量 innodb_flush_log_at_trx_commit 取值可以是 0,1,2

用来控制事务提交的时候,是否要刷新到 redo log buffer 到磁盘中;

  • 0: 标识事务提交不会向磁盘同步redo log
  • 1: 默认值,每次提交事务向磁盘同步redo log
  • 2: 每次提交事务都将redo log 写到操作系统的缓冲区中,但没有写入磁盘。

三种方式各有利弊:

  • 0:全靠后台线程手动操作,但是要面对数据库丢失的风险
  • 1:性能不如 0,但是保证了提交的事务数据不丢失
  • 2:性能介于0,1之间,数据库挂了但是只要操作系统不挂,持久性还是能得到保证的。

Checkpoint:

redo log的空间是有限的,但是redo log 是一个环,通过擦除 没用的信息 达到空间的重复利用,具体实现过程比较复杂,redo log 是为了防止 Buffer Pool 中的脏页丢失而设计的,那么如果随着系统运行,Buffer Poll的脏页刷新到了磁盘中,那么对应的redo log 对应的记录也就没用了,一次 checkpoint 的过程就是脏页刷新到磁盘中的过程,同时会标记redo log中哪些记录可以被覆盖。

有redo log 崩溃就不会导致丢失么

如果设置为0或者2的情况下,分别遇到数据库服务崩溃和操作系统崩溃两种情况下,数据都有可能崩溃

Licensed under CC BY-NC-SA 4.0
Built with Hugo
Theme Stack designed by Jimmy