写点什么

面经精选:数据库高频面试十问

作者:王中阳Go
  • 2024-08-12
    北京
  • 本文字数:6978 字

    阅读完需:约 23 分钟

1.InnoDB 和 MyISAM 存储引擎的区别?

MySQL 默认的存储引擎是 InnoDB,它采用 B+Tree 作为索引的数据结构。


在创建表时,InnoDB 存储引擎默认会创建一个主键索引,也就是聚簇索引,其它索引都属于二级索引。


MySQL 的 MyISAM 存储引擎支持多种索引数据结构,比如 B+ 树索引、R 树索引、Full-Text 索引。MyISAM 存储引擎在创建表时,创建的主键索引默认使用的是 B+ 树索引。


InnoDB 存储引擎有 2 个文件:Frm 文件和 Ibd 文件。Frm 文件是表的定义文件,而 Ibd 文件是数据和索引存储文件(数据以主键进行聚集索引,把真正的数据保存在叶子节点中)。


MyISAM 存储引擎有 3 个文件:Frm 文件、MYD 文件和 MYI 文件。Frm 文件是表的定义文件,MYD 文件是数据文件(所有的数据保存在这个文件中),MYI 文件是索引文件。


综上所述,InnoDB 和 MyISAM 都支持 B+ 树索引,但是它们数据的存储结构实现方式不同。不同之处在于:


  • InnoDB 存储引擎:B+ 树索引的叶子节点保存数据本身,即数据和索引都放在一个文件中;

  • MyISAM 存储引擎:B+ 树索引的叶子节点保存数据的物理地址,即两个文件分开存储;

2.聚合索引和非聚合索引的区别,以及各自的优缺点?

InnoDB 存储引擎根据索引类型不同,分为聚簇索引(上图就是聚簇索引)和二级索引。它们区别在于,聚簇索引的叶子节点存放的是实际数据,所有完整的用户数据都存放在聚簇索引的叶子节点,而二级索引的叶子节点存放的是主键值,而不是实际数据

聚簇索引的优点:

  • 当你需要取出一定范围内的数据时,用聚簇索引比用非聚簇索引好

  • 数据访问更快,聚集索引将索引和数据保存在同一个 B-Tree 中,因此从聚集索引中获取数据通常比在非聚集索引中查找要快。

  • 当通过聚簇索引查找目标数据时理论上比非聚簇索引要快,因为非聚簇索引定位到对应主键时可能还要多一次回表操作

  • 使用覆盖索引扫描的查询可以直接使用叶节点中的主键值

聚簇索引的缺点:

  • 插入速度严重依赖于插入顺序

  • 更新主键的代价很高,因为将会导致被更新的行移动

非聚簇索引的优点:

  • 插入和更新数据时不需要移动其他数据行,因此性能较好。

  • 非聚簇索引能够加速数据查询,提高查询速度。

非聚簇索引的缺点:

  • 查询非索引列的时候,需要进行二次查找,因此相对于聚簇索引,查询速度较慢。

  • 非聚簇索引的叶子节点不存储数据行,因此对于需要查询全部列的查询语句,需要进行额外的 I/O 操作,降低查询效率。

3.索引失效情景?

  • 当我们使用左或者左右模糊匹配的时候,也就是 like %xx 或者 like %xx% 这两种方式都会造成索引失效。

  • 如果查询条件中对索引字段使用函数,就会导致索引失效。

  • 在查询条件中对索引进行表达式计算,也是无法走索引的。

  • 如果索引字段是字符串类型,但是在条件查询中,输入的参数是整型的话会发生隐式类型转换,此时也是没法走索引的。

  • 如果不符合最左匹配原则,也就无法匹配上联合索引,联合索引就会失效。

  • 在 WHERE 子句中,如果在 OR 前的条件列是索引列,而在 OR 后的条件列不是索引列,那么索引会失效。

4.说一说 B+T 是一个怎么样的数据结构?相对于平衡二叉树、红黑树、跳表,以及 BT 来说,为什么使用 B+T?

  • B+T 一个节点有多个叶子节点,并且非叶子节点只存储索引,叶子节点存放实际数据;叶子节点间用双向链表维护,实现了高效的范围查询。

为什么使用 B+T

MySQL 是基于磁盘的数据库,磁盘的性能瓶颈在于磁盘 IO,我们知道磁盘是按照页进行存取的,每一页是固定大小,比如 16KB,对于平衡二叉树和红黑树等,当数据量大的时候,它们的树通常是很高的,每次查询都只能取一个节点放入内存中查找,这样就会增加 IO 次数,查询效率低下。因此 B 树一族的优化思路就是不再限制一个节点就只能有 2 个子节点,而是允许 M 个子节点 (M>2),从而降低树的高度。


B 树的每一个节点最多可以包括 M 个子节点,M 称为 B 树的阶,所以 B 树就是一个多叉树。每一次都取一批节点放入内存中查找,极大降低了磁盘 IO。


相对于 B 树,B+树做了两方面优化,一方面是非叶子节点只存放索引,另一方面是叶子节点间使用双向链表维护。对于非叶子节点的优化,其实还是针对减少磁盘 IO 的进一步优化。磁盘 IO 每一页的大小是固定的,我们希望一页存取的元素个数越多,那么每个元素的大小就得越小,因此 B+树的优化就是非叶子节点不再存放完整记录,这样节点的大小就极大减小了,磁盘 IO 次数也就减少了,进一步提升了查询效率。针对于叶子节点用双向链表维护,这种设计对范围查找非常有帮助。而 B 树没有将所有叶子节点用链表串联起来的结构,因此只能通过树的遍历来完成范围查询,这会涉及多个节点的磁盘 I/O 操作,范围查询效率不如 B+ 树。

5.事务的隔离级别分别怎么实现的?

  • 对于「读未提交」隔离级别的事务来说,因为可以读到未提交事务修改的数据,所以直接读取最新的数据就好了;

  • 对于「串行化」隔离级别的事务来说,通过加读写锁的方式来避免并行访问;

  • 对于「读提交」和「可重复读」隔离级别的事务来说,它们是通过 *Read View 来实现的,它们的区别在于创建 Read View 的时机不同,大家可以把 Read View 理解成一个数据快照,就像相机拍照那样,定格某一时刻的风景。 *** 「读提交」隔离级别是在「每个语句执行前」都会重新生成一个 Read View,而「可重复读」隔离级别是「启动事务时」生成一个 Read View,然后整个事务期间都在用这个 Read View

  • 注意,执行「开始事务」命令,并不意味着启动了事务。在 MySQL 有两种开启事务的命令,分别是:

  • 这两种开启事务的命令,事务的启动时机是不同的:

  • 执行了 begin/start transaction 命令后,并不代表事务启动了。只有在执行这个命令后,执行了第一条 select 语句,才是事务真正启动的时机;

  • 执行了 start transaction with consistent snapshot 命令,就会马上启动事务。

  • 第一种:begin/start transaction 命令;

  • 第二种:start transaction with consistent snapshot 命令;

6.可重复读解决幻读问题了吗?

首先说一下什么是幻读。当同一个查询在不同的时间产生不同的结果集时,事务中就会出现所谓的幻象问题。例如,如果 SELECT 执行了两次,但第二次返回了第一次没有返回的行,则该行是“幻像”行。


可重复读隔离级是由 MVCC(多版本并发控制)实现的,实现的方式是开始事务后(执行 begin 语句后),在执行第一个查询语句后,会创建一个 Read View,后续的查询语句利用这个 Read View,通过这个 Read View 就可以在 undo log 版本链找到事务开始时的数据,所以事务过程中每次查询的数据都是一样的,即使中途有其他事务插入了新纪录,是查询不出来这条数据的,所以就很好了避免幻读问题。


MySQL 里除了普通查询是快照读,其他都是当前读,比如 update、insert、delete,这些语句执行前都会查询最新版本的数据,然后再做进一步的操作。


针对当前读是仍存在幻读问题的,因为行锁并不影响其他事务的插入操作。因此 Innodb 引擎为了解决「可重复读」隔离级别使用「当前读」而造成的幻读问题,就引出了间隙锁,通过 记录锁+间隙锁形成 next-key lock(临键锁)的方式解决了幻读。


但是可重复读隔离级别下仍然没彻底解决幻读问题,举两个例子:


  • 对于快照读, MVCC 并不能完全避免幻读现象。因为当事务 A 更新了一条事务 B 插入的记录,那么事务 A 前后两次查询的记录条目就不一样了,所以就发生幻读。

  • 对于当前读,如果事务开启后,并没有执行当前读,而是先快照读,然后这期间如果其他事务插入了一条记录,那么事务后续使用当前读进行查询的时候,就会发现两次查询的记录条目就不一样了,所以就发生幻读。


所以,MySQL 可重复读隔离级别并没有彻底解决幻读,只是很大程度上避免了幻读现象的发生。


要避免这类特殊场景下发生幻读的现象的话,就是尽量在开启事务之后,马上执行 select ... for update 这类当前读的语句,因为它会对记录加 next-key lock,从而避免其他事务插入一条新记录。

7.数据库都有哪些锁?

一、按锁的区间划分


  1. 间隙锁(gap locks)

  2. 是开区间的,是一个在索引记录之间的间隙上的锁。

  3. 作用是保证某个间隙内的数据在锁定情况下不会发生任何变化。例如在默认隔离级别(可重复读)下,当使用非唯一索引搜索或没有索引时等情况会产生间隙锁。

  4. 临键锁(next - key locks)

  5. 是行锁 + 间隙锁,即临键锁是是一个左开右闭的区间。

  6. InnoDB 的默认事务隔离级别是可重复读,在这种级别下,如果使用特定语句(如select... for update 等)会触发临键锁,可以防止幻读。


二、按锁的粒度划分


  1. 表级锁(table - level lock)

  2. 直接给整个表添加锁。如 select * from student where name = 'tom' for update(InnoDB 在不通过索引检索数据时也是表锁 )。

  3. 开销小,加锁快;不会出现死锁;但锁定粒度大,发生锁冲突的概率最高,并发度最低。

  4. MyISAM 在执行查询语句(select)前,会自动给涉及的所有表加读锁,在执行更新操作(update、delete 、insert 等)前,会自动给涉及的表加写锁。

  5. 行级锁(record locks)

  6. InnoDB 中给指定的行添加锁:如 select * from student where id > 10 for update

  7. 是通过给索引上的索引项加锁来实现的,如果没有索引则会类似表锁(比如通过隐藏的聚簇索引) 。

  8. 行锁的劣势是开销大、加锁慢、会出现死锁;优势是锁的粒度小,发生锁冲突的概率低;处理并发的能力强。

  9. 页级锁

  10. 页级锁的颗粒度介于行级锁与表级锁之间。

  11. 主要应用于 BDB 存储引擎(现在使用相对较少)。


三、按锁级别划分


  1. 共享锁(share lock,即 S 锁)

  2. 又称读锁,允许一个事务去读取一行,阻止其他事务获得相同数据集的排它锁。若事务 t 对数据对象 a 加上 S 锁,则事务 t 可以读 a,但不能修改 a,其他事务只能对再对 a 加 S 锁,而不能加 X 锁 ,直到 t 释放 a 上的锁。这保证了其他事务可以读 a,但在释放 a 上的 S 锁之前不能对 a 做任何修改。

  3. 排它锁 / 独占锁(exclusive lock,即 X 锁)

  4. 又称写锁,允许获取排它锁的事物更新数据,阻止其他事务取得相同的数据集共享读锁和排它写锁。若事务 t 对数据对象 a 加上 X 锁,事物 t 可以读 a 也可以修改 a,其他事务不能再对 a 加任何锁,直到 t 释放 a 上的锁。

  5. 意向锁

  6. 意向共享锁(IS):表示事务准备给数据行加入共享锁,也就是说一个数据行加共享锁前必须先取得该表的 IS 锁;

  7. 意向排他锁(IX):类似上面,表示事务准备给数据行加入排他锁,说明事务在一个数据行加排他锁前必须先取得该表的 IX 锁。意向锁是 InnoDB 自动加的,不需要用户干预。


四、按加锁方式分类


  1. 自动锁(automatic locks) :数据库自动根据操作和场景加的锁。

  2. 显示锁(lock tables) :通过特定的命令(如lock tables... )手动显示加的锁。


五、按锁的使用方式分类


  1. 乐观锁(optimistic lock) : 并不是真正的锁机制,通常是通过在表中增加版本号等字段来实现,在更新时检查版本等标识是否符合预期来判断是否发生并发冲突等。

  2. 悲观锁(pessimistic lock) :通过实实在在的锁来控制并发访问,如前面提到的共享锁、排它锁等都属于悲观锁策略。


六、其他特殊锁


  1. 死锁:两个或多个事务相互等待对方持有的资源,从而导致都无法继续执行的情况。

  2. 全局锁:

  3. 对整个数据库实例加锁,让整个数据库处于只读状态。如 MySQL 提供了flush tables with read lock(ftwrl) 命令加全局读锁,加锁之后整个数据库实例处于只读状态,相关数据操作命令都会被阻塞。一般仅用于全库备份等特殊场景(且在 InnoDB 等支持一致性读的引擎中全库备份不一定需要 )。

8.说一说意向锁?

首先为什么需要引入意向锁呢?

我们在引入意向锁前有这样一个场景:其他事务对当前数据表进行了锁行操作(独占锁),而我们当前事务需要对该表加表级锁(独占表锁)。此时我们能加表锁的前提是当前表不存在独占锁,就需要遍历表里所有记录,查看是否有记录存在独占锁,这样效率会很慢。那么有了「意向锁」,由于在对记录加独占锁前,先会加上表级别的意向独占锁,那么在加「独占表锁」时,直接查该表是否有意向独占锁,如果有就意味着表里已经有记录被加了独占锁,这样就不用去遍历表里的记录。

那么什么是意向锁呢

  • 在使用 InnoDB 引擎的表里对某些记录加上「共享锁」之前,需要先在表级别加上一个「意向共享锁」;

  • 在使用 InnoDB 引擎的表里对某些纪录加上「独占锁」之前,需要先在表级别加上一个「意向独占锁」;


也就是,当执行插入、更新、删除操作,需要先对表加上「意向独占锁」,然后对该记录加独占锁。


而普通的 select 是不会加行级锁的,普通的 select 语句是利用 MVCC 实现一致性读,是无锁的。

意向锁带来的影响有哪些呢?(优点)

意向共享锁和意向独占锁是表级锁,不会和行级的共享锁和独占锁发生冲突,而且意向锁之间也不会发生冲突,只会和共享表锁(lock tables ... read)和独占表锁(lock tables ... write)发生冲突。因此引入了意向锁不仅解决了上述问题,而且并发性能也是很高的。

9.Redo Log 和 Binlog 的区别?

Redo Log 和 BinLog 是 MySQL 中两种重要的日志,它们的区别如下:


  • 功能

  • Redo Log 主要用于实现事务的持久性,确保在数据库发生故障(如停电、系统崩溃等)时,能够恢复未完成事务对数据的修改,从而保证数据不会丢失。

  • BinLog 则用于记录数据库的变更操作,包括数据的插入、更新和删除等,主要用于数据备份、主从复制和数据恢复等场景。

  • 写入方式

  • Redo Log 是循环写入的,空间固定大小,写满时会覆盖旧的日志。

  • BinLog 可以通过配置来控制文件大小,当达到一定大小或其他条件时,会生成新的文件进行续写。

  • 存储位置

  • Redo Log 是存储在 InnoDB 存储引擎特有的文件中。

  • BinLog 是存储在服务器级别的日志文件中。

  • 写入时机

  • redo log 写入时机(InnoDB 存储引擎层面)

  • 在事务执行过程中:

  • 事务中的操作会先在内存中的 redo log buffer(重做日志缓冲)中记录相应的 redo log 条目。 以下是后续几种触发刷到磁盘(redo log 文件)的情况:

  • 由参数innodb_flush_log_at_trx_commit控制:

  • 当设置为 1(默认):每次事务提交时都将 redo log buffer 中的 redo log 强制持久化到磁盘。

  • 当设置为 0:每次事务提交的时候都只是把 redo log 留在 redo log buffer 中;后台线程每隔 1s 进行一次刷盘操作,但如果 MySQL 进程崩溃可能丢失 1s 内的事务日志 。

  • 当设置为 2 :每次事务提交时都只是把 redo log 写到 page cache(文件系统缓存),由操作系统决定何时刷到磁盘;如果 MySQL 进程崩溃,操作系统不崩溃则数据不会丢失,但如果系统崩溃则可能丢失 1s 内的数据(因为后台线程 1s 刷新一次)。

  • 当 redo log buffer 占用的空间即将达到innodb_log_buffer_size一半的时候,后台线程会主动写盘(write,没有 fsync)。

  • 并行的事务提交的时候,顺带将这个事务的 redo log buffer 持久化到磁盘(事务 a 执行一半,部分 redo log 到 buffer 中;事务 b 提交,且 innodb_flush_log_at_trx_commit 设置为 1 或 2,会把 redo log buffer 里的 log 全部持久化到磁盘中)。

  • binlog 写入时机

  • 在事务执行期间:

  • 先把日志写到 binlog cache(每个线程都有自己的 binlog cache,binlog cache 大小由参数binlog_cache_size控制,如果超过了这个大小就要暂存磁盘)。

  • 在事务提交时:

  • 执行器把 binlog cache 里完整的事务写入 binlog 文件中,并清空 binlog cache。

  • 由参数sync_binlog控制真正刷盘(write 后进行 fsync)的时机:

  • 当 sync_binlog = 0 时,每次提交事务都只 write,不 fsync;如果操作系统崩溃,可能丢失部分事务的 binlog 。

  • 当 sync_binlog = 1 时,每次提交事务都会执行 fsync,保证 binlog 完整持久化;这是最安全但性能相对低的方式。

  • 当 sync_binlog = n(n > 1)时,表示每次提交事务都 write,但累积 n 个事务后才 fsync;这种方式可以减少磁盘 IO 操作次数提升性能,但如果主机发生异常重启,会丢失最近 n 个事务的 binlog 日志。

10.Binlog 有哪几种日志格式?

1. statement(基于语句的复制,SBR )


优点


  • 每一条会修改数据的 SQL 语句都会被记录在 binlog 中,日志量相对较小(如果不是批量、整表等操作情况下),相比于 row 格式在很多常规单一语句执行时节省空间和 I/O 。

  • 直观显示执行的语句,方便理解。


缺点


  • 一些语句在主从环境下可能出现不一致结果,例如使用了不确定函数(如 UUID() 每次生成不同值 )等情况。

  • 特定存储过程、函数、触发器调用和触发在从库可能无法正确复制。

  • 对于有依赖上下文的语句(如依赖于当前数据分布等)可能导致主从结果不同。


2. row(基于行的复制,RBR)


优点


  • 不记录 SQL 语句上下文信息,只记录数据行的变化情况(哪条记录被修改以及被修改成什么样等)。

  • 能准确地进行数据复制,不会出现由于语句逻辑在主从不同环境下的不一致性问题。


缺点


  • 产生的日志内容会非常多,特别是在执行批量更新、整表删除、alter 表等操作时,日志量极大,会造成大量的磁盘 I/O 开销。

  • 查看日志相对不够直观,不能直接看出执行的语句逻辑。


3. mixed(混合模式,MBR)


从 MySQL 5.1.8 版本开始推出。


特点和运行机制


  • 是 statement 和 row 的混合体。

  • 系统自动判断语句该用 statement 还是 row 格式来记录日志。一般情况下,常规的语句修改使用 statement 格式保存 binlog ;对于一些 statement 无法准确完成主从复制的复杂操作(如函数使用等导致潜在不一致的语句 )则采用 row 格式保存 binlog 。

  • 可以一定程度上兼顾日志量和数据复制的准确性。但有时在切换格式时可能会存在一些边界情况需要关注和处理(比如切换瞬间数据的一致性保障等 )。

欢迎关注 ❤

我们搞了一个免费的面试真题共享群,互通有无,一起刷题进步。


没准能让你能刷到自己意向公司的最新面试题呢。


感兴趣的朋友们可以加我微信:wangzhongyang2024,备注:infoq 面试群。

用户头像

王中阳Go

关注

靠敲代码在北京买房的程序员 2022-10-09 加入

【微信】wangzhongyang1993【公众号】程序员升职加薪之旅【成就】InfoQ专家博主👍掘金签约作者👍B站&掘金&CSDN&思否等全平台账号:王中阳Go

评论 (1 条评论)

发布
用户头像
欢迎大家给我分享最新的面经,有偿的哦
刚刚 · 北京
回复
没有更多了
面经精选:数据库高频面试十问_数据库_王中阳Go_InfoQ写作社区