MySQL面试题

74 阅读15分钟

MySQL

架构

请说下你对MySQL架构的了解?

MySQL可以分为Server层和存储引擎两部分,Server层包括连接器、查询缓存、分析器、优化器、执行器,负责了MySQL的大多数核心功能,存储引擎负责数据段提取和存储,架构是插件式的,支持InnoDB、MyISAM等多个存储引擎。

数据库的三范式

第一范式:列的原子性,数据库的每一列都是不可再分割的原子数据项;

第二范式:列要完全依赖于主属性,而不能仅依赖一部分

第三范式:任何非主属性不依赖于其他非主属性

SQL语句

char和varchar的区别

char长度固定,varchar可变长度,存储的是每个值占用的字节和一个用来记录总字节长度的值。在存放相同数据时,varchar(10)和varchar(20)在排序上后者会占用更多空间。

MySQL锁有哪些?

从锁的粒度分类:行锁、表锁、间隙锁

从资源角度分类:共享锁(读锁)、排他锁(写锁)

从并发解决角度分类:乐观锁(通过版本号实现)、悲观锁(行锁、表锁)

记录锁:也就是行锁,事务独占,避免其他事务插入。

间隙锁(Gap Lock):会锁住两个索引之间的区间,在RR隔离级别下才会触发,还需要满足以下条件之一:

  1. 使用普通索引锁定
  2. 使用多列唯一索引
  3. 使用唯一索引锁定多行记录

临建锁(Next-Key Lock):记录锁和间隙锁的组合,查询的记录锁住、该范围查询内的所有间隙空间也会锁住,再加上相邻的下一个区间。可以解决幻读问题,在RC隔离级别也会失效。

页锁:锁定粒度介于行级锁和表级锁中间的一种锁,一次锁定相邻的一组记录。开销和加锁时间界于表锁和行锁之间;会出现死锁。

事务

描述一下事务的特性

  • Atomic原子性:事务是最小的执行单位,不允许分割,事务中的动作要么全部完成,要么完全不起作用
  • Consistency一致性:执行事务前后,数据库从一个一致性状态转换到另一个一致性状态
  • Isolation隔离性:并发访问数据库时,事务之间相互独立
  • Durable持久性:一个事务提交后,其数据的改变是持久的

InnoDB是如何保证事务的?

原子性:redo/undo机制

隔离性:MVCC多版本并发控制

持久性:重做日志redo log

原子性、隔离性、持久性都是为了保障一致性而存在的,一致性也是最终的目的

MySQL的三大日志

binlog归档日志、redo log重做日志、undo log回滚日志

redo log

redo log(重做日志)是InnoDB存储引擎独有的一种物理日志,它记录了“在某个数据页上做了什么修改”,它让MySQL拥有了崩溃恢复能力。用 redo log 形式记录修改内容(批量的数据修改+顺序写),性能会远远超过刷数据页(随机写)的方式,这也让数据库的并发能力更强。

默认情况下,每次事务提交时都将进行刷盘操作(调用 fsync )。另外,InnoDB 存储引擎有一个后台线程,每隔1 秒,就会把 redo log buffer 中的内容写到文件系统缓存(page cache),然后调用 fsync 刷盘。

下图为innodb_flush_log_at_trx_commit=1(默认)时的情况

此策略下,只要事务提交成功,redo log记录就一定在硬盘里,不会有任何数据丢失。如果事务执行期间MySQL挂了或宕机,这部分日志丢了但由于事务并没有提交,所以也问题不大。

binlog

binlog属于MySQL Server层,是逻辑日志。主要用来数据备份、主从同步。

记录格式:

  • statement:其中statement记录的是SQL语句原文,但这种会出现问题,比如获取当前系统时间。
  • row:记录的内容包括SQL语句和操作的具体数据
  • mixed:前两者的混合

写入机制:

事务执行过程中,先把日志写到binlog cache中,事务提交的时候,再把binlog cache写到binlog文件中,写入文件系统缓存page cache,然后通过fsync持久化到磁盘。

write和fsync的时机由参数 sync_binlog 控制(默认为0)。

性能优先:每次提交事务只write,由系统决定什么时候执行fsync,缺点是机器宕机会导致文件缓存系统中的binlog丢失,一般用在从数据库里。

安全优先:可以设为1,每次提交事务都会执行fsync。

折中的方案:为N的话则积累N个事务才fsync。

undo log

如果要保证事务的原子性,就需要在异常发生时,对已经执行的操作进行回滚,在MySQL中,恢复机制是通过回滚日志实现的,所有事务进行的修改都会先记录到这个回滚日志中,再执行相关的操作,如果执行过程中遇到异常,直接利用回滚日志中的信息将数据回滚到修改之前的样子。

redo log和binlog的区别

两者都属于持久化的保证,但从功能上说redo log主要是为数据恢复做准备,binlog是为了保证MySQL集群架构的一致性。

在执行更新语句过程,会记录redo log和binlog两块日志,以基本的事务为单位,redo log在事务执行过程中可以不断写入,而binlog只有在提交事务时才写入,两者的写入时机不一致。

两阶段提交是什么?

执行事务的过程中记录好了redo log,binlog日志写期间发生了异常,会导致两份日志的逻辑不一致。

我们可以将redo log的写入拆成两个阶段prepare和commit,这就是两阶段提交。

事务隔离级别有哪些?

读未提交、都提交、可重复读、串行化

  1. 读未提交会读到另一个事务的未提交数据,产生脏读问题
  2. 读提交解决了脏读的问题,出现了不可重复读,即在一个事务多次读取到的数据可能不一样
  3. 可重复读解决了不可重复读和脏读的问题,但存在幻读的问题,一般是在针对insert操作,在一个事务内读取两次数据行数不同(MySQL的默认实现)
  4. 串行化解决了幻读的问题,要求事务的执行过程为顺序执行,性能会受到很大影响。

为什么隔离级别越高,性能越差?

这和各隔离级别的加锁机制有关。

  • 读未提交没有加任何锁
  • 串行化加的是表锁、读的时候加共享锁,写的时候加的是排他锁,因此没有并发性可言
  • 读提交和可重复读兼顾数据问题和并发性,采用的是MVCC方式实现,可重复读级别在事务开始时创建一致性视图,读提交在每一个语句执行前都会重新计算出一个新的视图。

索引

谈谈你对索引的理解?

索引的出现是为了提高数据的查询效率,但也会带来增删改过程中维护索引的额外开销,同时在空间上也会占用除了数据本身之外的额外物理空间。

我们可以通过在频繁使用的、需要排序的字段上建立索引提高部分特定查询的查询效率,不应在索引区分度小(涉及的列很少或者重复值较多)的列上、文本字段列上建立索引。

索引查询失效的情况有哪些?

  1. 模糊查询时不满足最左匹配原则
  2. or语句前后没有同时使用索引
  3. 联合索引中未用到第一列的索引
  4. 数据出现隐式转化
  5. 索引参与函数、表达式运算
  6. 正则表达式不适用索引
  7. 全表扫描的速度比索引快时

实际开发中,可以用explain+sql语句查看执行计划,具体看此次查询中可能选用的索引和确切使用的索引

索引的底层数据结构?

和具体的存储引擎实现有关,MySQL中使用较多的有Hash索引、B+树索引等,InnoDB的默认实现是B+树索引。

B+树具有B树的平衡性,又加入了叶子节点顺序访问指针。在查询时,首先在根节点二分查找,找到一个key所在的指针,然后递归地在指针所指向的节点进行查找,直到找到叶子节点,在叶子节点进行二分查找,最后得到需要的data;在增删改操作下,要通过对树的分裂、合并、旋转等操作来维护平衡性。

哈希索引是什么?

InnoDB存储引擎有一个自适应哈希索引的功能,当某个索引值被使用的很频繁时,会在B+树索引之上再创建一个哈希索引,支持以O(1)时间进行快速查找,但该索引支支持精确查找。

为什么InnoDB引擎使用B+树,而不是用B树?

  1. B+树所有数据存储在叶子节点上,更容易进行全盘扫描,效率更高,而数据库中基于范围的查询是非常频繁的。
  2. B+树的查询效率更加稳定,由于内部节点不保存具体的数据,每次查询都必须走一条从根节点到叶子结点的完整路径。
  3. B+树的磁盘读写代价更低。在数据量大时,不能把整个索引全部加载到内存中,只能逐一从磁盘中加载每次要查询的指针。B树由于内部节点存储数据,节点较大、B+树只有叶子节点存储数据,内部节点较小,可以存放更多的下一层节点的指针,因此相同数据量下B+树高度会更低,IO就不那么频繁。(磁盘预读取时以块为单位,若一块磁盘块中包含了树节点以外的数据就会造成浪费,我们设计时使每一个节点的大小刚好或接近一个磁盘块的大小,16k)。

谈谈你对聚簇索引的理解?

数据的物理存储顺序于索引顺序一致,一般情况下,主键默认创建聚簇索引,一张表只允许存在一个聚簇索引。聚簇索引的叶子节点就是行数据,非聚簇索引的叶子节点存储的是主键值或行数据存储的物理位置

谈谈你对覆盖索引的认识?

如果一个索引包含了满足查询语句中的所有字段的数据就叫做覆盖索引。

优点:

  1. 索引通常远小于数据行,只读取索引能减少数据访问量。
  2. 对于InnoDB引擎,辅助索引能够覆盖查询就能避免在访问一次主索引。
  3. 对于MyISAM引擎,索引与数据文件分开存储,每次用索引访问行数据都需要经过系统调用,如果缓存中覆盖了查询值就能避免一次系统调用。

索引的分类

从数据结构的角度:树索引、哈希索引

从物理存储的角度:聚簇索引、非聚簇索引

从逻辑的角度:普通索引、唯一索引、主键索引、联合索引、全文索引

为什么开发中避免使用select * from

  1. 不会使用覆盖索引,导致查询效率变低
  2. 会导致读取IO量比较大,影响查询性能
  3. 可读性差

什么是最左前缀原则?

建立联合索引时,建立了一个(a,b)索引就等同于建立了(a)和(a,b),B+树是按照从左到右的顺序来建立搜索树的。

查找时在B+树上会从左开始一直向右匹配直到遇到范围查询就停止匹配,如果遇到缺失的字段,则索引就只在这前面起作用

引擎

InnoDB和MyISAM的区别?

  • InnoDB支持事务;MyISAM不支持事务
  • InnoDB支持外键;MyISAM不支持外键
  • InnoDB的主键索引是聚簇索引;MyISAM全都是非聚集索引
  • InnoDB可以支持行锁;MyISAM只支持表锁

谈谈MVCC?

数据库并发场景有读读、读写、写写。读读不会发生问题不需要并发控制,读写会出现脏读幻读不可重复读等问题,写写会出现更新丢失的问题,MVCC是一种解决读写冲突的无锁并发控制。

InnoDB中每一个事务都有一个自己的事务id,并且是唯一的、递增的,MySQL的每一个数据行都有可能存在多个版本,在每次事务更新数据时,都会生成一个新的数据版本,并且把自己的事务id赋给当前数据行。

并发读: 对于一个事务视图来说,除了对自己更新的总是可见,还有三种情况:版本未提交的,都是不可见的;版本已经提交,但是是在创建视图之后提交的也是不可见的;版本已经提交,若是在创建视图之前提交的是可见的。

并发写: 事务1和事务2都要执行update的操作,则争抢互斥行锁,获取不到行锁的事务就会wait,直到获取到锁或超时异常。若update的数据没有索引,则需要获取所有行,加上行锁,MySQL再过滤出所有符合条件的行并释放不需要的锁,这么做性能消耗会很大。

可以解决的问题有:

  1. 提高读写操作的并发度
  2. 解决脏读幻读不可重复读的问题,但不能解决更新丢失问题

幻读如何解决?

幻读指的是一个事务在前后两次查询同一个范围的时候,后一次查询看到了前一次查询没有看到的行。

快照读(普通的查询):由于MVCC的特性,不会看到别的事务插入或删除的数据。

当前读(for update行锁):只是把当前存在的记录加上锁,不能解决插入新数据的情况,所以引入了间隙锁

间隙锁和行锁合称next-key lock,每个next-key lock是前开后闭区间

间隙锁的引入由于锁住了更大的范围,会影响并发度

(扩展:间隙锁在可重复读隔离级别下才会生效,因此如果把隔离级别设置为读提交的话就没有间隙锁了,但同时为了保证数据和日志的不一致问题,需要把binlog格式设为row)

调优

如何找出慢查询语句?

使用explain语句,查看索引使用情况。

MySQL慢查询应该如何优化?

要看是经常很慢还是偶尔很慢

偶尔慢可能是遇到binlog刷脏页

经常很慢就要:

  1. 检查是否走了索引,如果没有则优化SQL利用索引
  2. 检查所利用的索引,是否是最优的索引
  3. 检查所查字段是否有非必要的
  4. 检查表中数据是否过多,是否应该做分库分表
  5. 检查数据库实例所在的性能配置,是否太低,可以适当增加资源。

生产环境的MySQL

谈谈你对水平切分和垂直切分的理解

水平切分:将同一个表中的记录拆分到多个结构相同的表中,当数据量过多时,可以将数据分布到集群的不同的节点上,从而缓解单个数据库压力。

垂直切分:通常按照列的关系密集程度进行切分,讲常被使用的列和不常使用的列且分到不同的表中。

讲一下数据库的主从复制过程?

主库负责处理写,从库负责处理读,当主库的数据发生改变时,从库为保证数据库一致性必须也做相应的修改。过程如下:从库的I/O线程从主服务器上读取binlog,并写入从服务器端Relay log中;从库的SQL线程负责读取Relay log并执行SQL语句并写入本地的binlog中

主从同步的延迟问题怎么解决?

造成的原因:从服务器里面读取binlog的速度赶不上主服务器更新的速度,大量的SQL积压,导致了较长时间的主从不一致问题。

解决方案:

  1. 从库必须提高处理SQL的速度,比如调节参数使记录binlog的安全级别,即不一定要保证binlog的同步性,数据还是以主库为准。
  2. 增加从服务器,分散读的压力,从而降低服务器负载。

谈谈你对数据库读写分离的理解?

读写分离常用代理方式来实现,代理服务器接收应用层传来的读写请求,转发到对应的服务器。

能提高性能的原因:

  1. 读写分开,极大程度缓解了锁的争用
  2. 从服务器可以使用MyISAM,提高查询性能
  3. 增加冗余,提高可用性