计算机面经--【MySQL篇】

提示:本文章的内容来源于自己所学的一些知识以及网络


前言

博主理解:其实在Java后端面试中,所谓的“八股文”并不是让大家完整地背下来,只是将自己学会的知识,用自己的语言讲给面试官听,面试官希望听到的是你对这个知识点的理解,深度。“八股文”并不是一门技术,希望各位道友不要过多地痴迷,能够及时解决Bug以及根据需求选择合适的架构方案才是一个程序员基本素养。这个面经系列我会一直更新下去,如果对你有帮助,麻烦点个关注,谢谢!


1、MySQL常用的存储引擎有哪些?了解过吗?

关键字:支持事务、行级锁、外键 可以通过 show engines; 的方式查看对应的数据库支持的存储引擎。
在这里插入图片描述

  • InnoDB
    InnoDB是一种兼顾高可靠性和高性能的通用存储引擎,在 MySQL 5.5 之后,InnoDB是默认的MySQL 存储引擎。
    • DML操作遵循ACID模型,支持事务
    • 行级锁,提高并发访问性能;
    • 支持外键 FOREIGN KEY约束,保证数据的完整性和正确性
    • 文件:xxx.ibd:xxx代表的是表名(存储的是索引和数据,一起存储),innoDB引擎的每张表都会对应这样一个表空间文件,存储该表的表结构、数据和索引。
    • 逻辑存储结构(段–>区–>页–>行
      在这里插入图片描述
      • 表空间:InnoDB存储引擎逻辑结构的最高层,ibd文件其实就是表空间文件,在表空间中可以包含多个Segment段。
      • 段 : 表空间是由各个段组成的, 常见的段有数据段、索引段、回滚段等。InnoDB中对于段的管理,都是引擎自身完成,不需要人为对其控制,一个段中包含多个区。
      • 区 : 区是表空间的单元结构,每个区的大小为1M。 默认情况下, InnoDB存储引擎页大小为16K, 即一个区中一共有64个连续的页。
      • 页 : 页是组成区的最小单元,页也是InnoDB 存储引擎磁盘管理的最小单元,每个页的大小默认为 16KB。为了保证页的连续性,InnoDB 存储引擎每次从磁盘申请 4-5 个区。注意:B+树中的叶子节点存储的数据,实际上是页,页的逻辑结构下面会详细介绍
      • 行 : InnoDB 存储引擎是面向行的,也就是说数据是按行进行存放的,在每一行中除了定义表时所指定的字段以外,还包含两个隐藏字段。
  • MyISAM
    • 不支持事务,不支持外键
    • 支持表锁,不支持行锁
    • 访问速度快

关于InnoDB的页结构如下:
在这里插入图片描述
可以看出在页中对应着四条记录,页目录记录的是相应的主键索引最大值,这也就解释了在叶子节点中实际上存的是页(page),而每个页节点中存储的才是表中一行行数据,这里还牵涉到B+树在三层的时候能够存在多少行数据的问题,如下:
在这里插入图片描述
假设每个页的主键索引为int型,占用4字节,每页的地址占用6字节,加起来就是10字节,我们知道在B+树中,非叶子节点是不存数据的,只存索引,对应着是图中的绿色框,每页的容量固定为16kb,则绿色的框(第一层)可以管理1638页,这样就不难计算出两层的B+树能够存储多少行数据了。假设一页能够存8条数据(每个数据占用2kb)那么两层的B+树能够存数据1638*(16kb/2kb)=13104条数据,那么三层B+树存储的数据量为1638*13104=21464352,也就是两千万条数据,一般来讲希望MySQL的数据量不要超过两千万行,并且B+树不要超过三层,否则会导致查询效率变慢,此时就要采取分库分表的措施(后面我会介绍分库分表的知识点)。

如何选择?
​ 1、是否需要支持事务,如果需要选择innodb,如果不需要选择myisam
​ 2、如果表的大部分请求都是读请求,可以考虑myisam,如果既有读也有写,使用innodb
​ 现在mysql的默认存储引擎已经变成了Innodb,推荐使用innodb


2、什么是MySQL索引?MySQL常见的索引数据结构?Hash索引和B+树索引的区别,以及应用场景?

关键字:索引、哈希索引、B+树索引

  • 索引:是帮助数据库高效获取数据的一种数据结构。在数据库中,除了存储的数据,数据库系统还维护着各种各样的索引,以便帮助用户来获取数据(是一种典型的空间换时间的做法)。
  • MySQL的索引是在存储引擎层实现的,不同的存储引擎有不同的索引结构,主要包含以下几种(重点是B+树索引、哈希索引,一般来讲我们平常所说的索引,如果没有特别指明,都是指B+树结构组织的索引):
    在这里插入图片描述
  • Hash索引和B+树索引的区别:
    • Hash索引:利用的是哈希表,只能进行精确匹配不支持范围查询、模糊查询以及排序,存在哈希碰撞、查找性能不稳定问题。
    • B+树索引:每次查询都是从根节点出发,查到叶子结点,性能稳定支持索引、排序、范围查询和模糊查询等
  • 扩展(详细地介绍一下B+树):
    • 为什么不用二叉树或者红黑树作为索引单链表、层级深 B+树是普通二叉树的扩展,如果采用二叉树作为MySQL的索引结构,会存在如下的问题:1)顺序插入时,会形成一个链表,查询的性能大大降低。2)大数据量的情况下,层级较深,检索速度慢。可能大家会想到,普通的二叉树存在不平衡问题,那么采用自平衡二叉树,比如红黑树?红黑树是可以解决数据的平衡问题,但是红黑树也是二叉树,会存在一个缺点:1)大数据量的情况下,层级较深,检索速度变慢。所以,在MySQL的索引结构中,并没有选择二叉树或者红黑树,而选择的是B+Tree,那么什么是B+Tree呢?在详解B+Tree之前,先来介绍一个B-Tree。
      在这里插入图片描述

    • B-Tree的数据结构:·多叉树、中间元素分裂、所有节点存储数据 B树是一种多叉路衡查找树(多叉树),相对于二叉树,B树每个节点可以有多个分支,即多叉。以一颗最大度数(max-degree)为5(5阶)的b-tree为例,那这个B树每个节点最多存储4个key,5个指针:
      在这里插入图片描述

      注意:树的度数指的是一个节点的子节点个数。
      B-Tree演示网址 https://www.cs.usfca.edu/~galles/visualization/BTree.html

      特点:1)5阶的B树,每一个节点最多存储4个key,对应5个指针。2)一旦节点存储的key数量到达5,就会裂变,中间元素向上分裂。3)在B树中,非叶子节点和叶子节点都会存放数据。

    • B+Tree的数据结构叶子结点存数据、非叶子结点存索引、单向链表 B+Tree是B-Tree的变种,只要是在B树上做了改进的都可以称之为B+树。以一颗最大度数(max-degree)为4(4阶)的b+tree为例,来看一下其结构示意图:
      在这里插入图片描述
      我们可以看到,两部分:

      绿色框框起来的部分,是索引部分,非叶子结点,仅仅起到索引数据的作用,不存储数据。
      红色框框起来的部分,是数据存储部分,在其叶子节点中要存储具体的数据
      B+Tree演示网址 https://www.cs.usfca.edu/~galles/visualization/BPlusTree.html

      特点:1)所有的数据都会出现在叶子节点。2)叶子节点形成一个单向链表。3)非叶子节点仅仅起到索引数据作用,具体的数据都是在叶子节点存放的。

    • MySQL中优化后的B+Tree数据结构叶子结点存数据、非叶子结点存索引、双向链表 MySQL索引数据结构对经典的B+Tree进行了优化。在原B+Tree的基础上,增加一个指向相邻叶子节点的链表指针,就形成了带有顺序指针的B+Tree,提高区间访问的性能,利于排序。
      在这里插入图片描述


3、数据库索引原理?MySQL中有哪些索引?什么是聚簇索引?什么是非聚簇索引?非聚簇索引一定会回表查询吗?

  • 索引原理IO、数据结构
    在这里插入图片描述

  • 聚簇索引与非聚簇索引:Innodb存储引擎在进行数据插入的时候必须要绑定到一个索引列上,默认是主键,如果没有主键,会选择唯一键,如果没有唯一键,那么会选择生成6字节的row_id,跟数据绑定在一起的索引我们称之为聚簇索引,没有跟数据绑定在一起的索引我们称之为非聚簇索引(与引擎的存储文件有关,.idb文件是数据和索引放在一起;.MYD文件和.MYI文件是分别存储数据和索引的文件=>非聚簇索引)。
    ​Innodb存储引擎中既有聚簇索引也有非聚簇索引,而MyISAM存储引擎中只有非聚簇索引。

  • 回表查询


4、索引在什么情况下会失效?

  • 索引失效的情况
    • 联合索引不遵循最左匹配原则
    • 联合索引的前面索引列使用范围查询(<,>,like),会导致后续的索引失效
    • 不要在索引上做任何操作(计算,函数,类型转换)
    • 字符串不添加引号会导致索引失效(存在隐式类型转换)
    • like语句中,以%开头的模糊查询
    • is null和is not null无法使用索引
    • 尽量少使用or操作符,连接时索引会失效(取决于or链接的字段是否都使用索引)
    • 两表关联使用的条件字段中字段的长度、编码不一致会导致索引失效
    • 如果mysql中使用全表扫描比使用索引快,也会导致索引失效(优化器的结果,MySQL频繁地进行增删改查操作,导致数据库统计信息没有更新,统计失效,优化器选择出错,可以执行以下的SQL语句 optimize table xxx; 来强制更新统计信息,或者强制使用某个索引 force index,人工干预优化器的选择)

5、什么是最左匹配原则?


6、描述一下数据库事务隔离级别,分别解决了什么问题?

​ ACID:

​ 原子性:undo log(MVCC)

​ 一致性: 最核心和最本质的要求

​ 隔离性:锁,mvcc(多版本并发控制)

​ 持久性:redo log

​ 数据库的事务隔离级别有四种,分别是读未提交、读已提交、可重复读、序列化,不同的隔离级别下会产生脏读、幻读、不可重复读等相关问题,因此在选择隔离级别的时候要根据应用场景来决定,使用合适的隔离级别。

​ 各种隔离级别和数据库异常情况对应情况如下:

隔离级别脏读不可重复读幻读
READ- UNCOMMITTED
READ-COMMITTED×
REPEATABLE- READ××
SERIALIZABLE×××

SQL 标准定义了四个隔离级别:

  • READ-UNCOMMITTED(读取未提交): 事务的修改,即使没有提交,对其他事务也都是可见的。事务能够读取未提交的数据,这种情况称为脏读。
  • READ-COMMITTED(读取已提交): 事务读取已提交的数据,大多数数据库的默认隔离级别。当一个事务在执行过程中,数据被另外一个事务修改,造成本次事务前后读取的信息不一样,这种情况称为不可重复读。
  • REPEATABLE-READ(可重复读): 这个级别是MySQL的默认隔离级别,它解决了脏读的问题,同时也保证了同一个事务多次读取同样的记录是一致的,但这个级别还是会出现幻读的情况。幻读是指当一个事务A读取某一个范围的数据时,另一个事务B在这个范围插入行,A事务再次读取这个范围的数据时,会产生幻读
  • SERIALIZABLE(可串行化): 最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。

事务隔离机制的实现基于锁机制和并发调度。其中并发调度使用的是MVVC(多版本并发控制),通过保存修改的旧版本信息来支持并发一致性读和回滚等特性。

因为隔离级别越低,事务请求的锁越少,所以大部分数据库系统的隔离级别都是READ-COMMITTED(读取提交内容):,但是你要知道的是InnoDB 存储引擎默认使用 REPEATABLE-READ(可重复读)并不会有任何性能损失。


7、MySQL幻读怎么解决的

​事务A按照一定条件进行数据读取,期间事务B插入了相同搜索条件的新数据,事务A再次按照原先条件进行读取时,发现了事务B新插入的数据称之为幻读。(在数据库隔离级别为RR的情况下,执行同一条SQL语句出现数据不一致的情况,根本原因是因为快照读和当前读发生了混用,一般出现了幻读,才取加临键锁,next-key 来解决

CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) DEFAULT NULL,
  `age` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB ;

INSERT into user VALUES (1,'1',20),(5,'5',20),(15,'15',30),(20,'20',30);

假设有如下业务场景:

时间事务1事务2
begin;
T1select * from user where age = 20;2个结果
T2insert into user values(25,‘25’,20);commit;
T3select * from user where age =20;2个结果
T4update user set name=‘00’ where age =20;此时看到影响的行数为3
T5select * from user where age =20;三个结果

执行流程如下:

1、T1时刻读取年龄为20 的数据,事务1拿到了2条记录

2、T2时刻另一个事务插入一条新的记录,年龄也是20

3、T3时刻,事务1再次读取年龄为20的数据,发现还是2条记录,事务2插入的数据并没有影响到事务1的事务读取

4、T4时刻,事务1修改年龄为20的数据,发现结果变成了三条,修改了三条数据

5、T5时刻,事务1再次读取年龄为20的数据,发现结果有三条,第三条数据就是事务2插入的数据,此时就产生了幻读情况

此时大家需要思考一个问题,在当下场景里,为什么没有解决幻读问题?

其实通过前面的分析,大家应该知道了快照读和当前读,一般情况下select * from …where …是快照读,不会加锁,而 for update,lock in share mode,update,delete都属于当前读,会使得read-view发生改变,改变之后做select查询,读取的是最新的read-view,如果事务中都是用快照读,那么不会产生幻读的问题,但是快照读和当前读一起使用的时候就会产生幻读
如下图所示,为演示案例:
在这里插入图片描述
从图中可以看出,出现了幻读情况,最后我们执行相同的SQL语句:

select * from user where age = 20;

出现(此时数据库的隔离级别还是RR):
在这里插入图片描述

如果都是当前读的话,如何解决幻读问题呢(锁+MVCC)?

truncate table user;  // 清空表操作
INSERT into user VALUES (1,'1',20),(5,'5',20),(15,'15',30),(20,'20',30);
时间事务1事务2
begin;
T1select * from user where age =20 for update;
T2insert into user values(25,‘25’,20);此时会阻塞等待锁
T3select * from user where age =20 for update;

此时,可以看到事务2被阻塞了,需要等待事务1提交事务之后才能完成,其实本质上来说采用的是间隙锁的机制解决幻读问题。比如说执行如下SQL时候,

select * from user where age =20 for update; 

再执行查看InnoDB锁机制的命令,

show engine innodb status\G;

在这里插入图片描述


8、sql join原理?(冷门知识点)

MySQL是只支持一种Join算法Nested-Loop Join(嵌套循环连接),并不支持哈希连接和合并连接,不过在mysql中包含了多种变种,能够帮助MySQL提高join执行的效率。

1、Simple Nested-Loop Join

这个算法相对来说就是很简单了,从驱动表中取出R1匹配S表所有列,然后R2,R3,直到将R表中的所有数据匹配完,然后合并数据,可以看到这种算法要对S表进行RN次访问,虽然简单,但是相对来说开销还是太大了。

2、Index Nested-Loop Join

​索引嵌套联系由于非驱动表上有索引,所以比较的时候不再需要一条条记录进行比较,而可以通过索引来减少比较,从而加速查询。这也就是平时我们在做关联查询的时候必须要求关联字段有索引的一个主要原因。

这种算法在链接查询的时候,驱动表会根据关联字段的索引进行查找,当在索引上找到了符合的值,再回表进行查询,也就是只有当匹配到索引以后才会进行回表。至于驱动表的选择,MySQL优化器一般情况下是会选择记录数少的作为驱动表,但是当SQL特别复杂的时候不排除会出现错误选择。

在索引嵌套链接的方式下,如果非驱动表的关联键是主键的话,这样来说性能就会非常的高,如果不是主键的话,关联起来如果返回的行数很多的话,效率就会特别的低,因为要多次的回表操作。先关联索引,然后根据二级索引的主键ID进行回表的操作。这样来说的话性能相对就会很差。

3、Block Nested-Loop Join

​在有索引的情况下,MySQL会尝试去使用Index Nested-Loop Join算法,在有些情况下,可能Join的列就是没有索引,那么这时MySQL的选择绝对不会是最先介绍的Simple Nested-Loop Join算法,而是会优先使用Block Nested-Loop Join的算法。

Block Nested-Loop Join对比Simple Nested-Loop Join多了一个中间处理的过程,也就是join buffer,使用join buffer将驱动表的查询JOIN相关列都给缓冲到了JOIN BUFFER当中,然后批量与非驱动表进行比较,这也来实现的话,可以将多次比较合并到一次,降低了非驱动表的访问频率。也就是只需要访问一次S表。这样来说的话,就不会出现多次访问非驱动表的情况了,也只有这种情况下才会访问join buffer。

在MySQL当中,我们可以通过参数join_buffer_size来设置join buffer的值,然后再进行操作。默认情况下join_buffer_size=256K,在查找的时候MySQL会将所有的需要的列缓存到join buffer当中,包括select的列,而不是仅仅只缓存关联列。在一个有N个JOIN关联的SQL当中会在执行时候分配N-1个join buffer。


9、mysql如何做分库分表的?

使用mycat或者shardingsphere中间件做分库分表,选择合适的中间件,水平分库,水平分表(按行切分),垂直分库,垂直分表(按字段切分,热点字段和非热点字段)

​ 在进行分库分表的时候要尽量遵循以下原则:

​ 1、能不切分尽量不要切分;

​ 2、如果要切分一定要选择合适的切分规则,提前规划好;

​ 3、数据切分尽量通过数据冗余或表分组来降低跨库 Join 的可能(利用空间换时间);

​ 4、由于数据库中间件对数据 Join 实现的优劣难以把握,而且实现高性能难度极大,业务读取尽量少使用多表 Join(使用的话,在本库Join操作,不要跨库Join)。


10、描述一下MySQL一条数据是如何保存到数据库的?


11、描述一下mysql主从复制的机制的原理?mysql主从复制主要有几种模式?


12、在优化sql时,查询计划的结果中看哪些些关键数据?

企业的应用场景中,为了知道优化SQL语句的执行,需要查看SQL语句的具体执行过程,以加快SQL语句的执行效率。可以使用explain+SQL语句来模拟优化器执行SQL查询语句,从而知道mysql是如何处理sql语句的,详细操作见MySQL的执行计划

  • 执行计划中包含的信息
ColumnMeaning
idThe SELECT identifier
select_typeThe SELECT type
tableThe table for the output row
partitionsThe matching partitions
typeThe join type
possible_keysThe possible indexes to choose
keyThe index actually chosen
key_lenThe length of the chosen key
refThe columns compared to the index
rowsEstimate of rows to be examined
filteredPercentage of rows filtered by table condition
extraAdditional information

13、描述一下mysql的乐观锁和悲观锁,锁的种类?

乐观锁并不是数据库自带的,如果需要使用乐观锁,那么需要自己去实现,一般情况下,我们会在表中新增一个version字段,每次更新数据version+1,在进行提交之前会判断version是否一致。我们所说的加行锁,重点在于理解是给索引加锁

​ mysql中的绝大部分锁都是悲观锁,按照粒度可以分为行锁和表锁:

行锁:

​ 共享锁:当读取一行记录的时候,为了防止别人修改,则需要添加S锁

​ 排它锁:当修改一行记录的时候,为了防止别人同时进行修改,则需要添加X锁

XS
X不兼容不兼容
S不兼容兼容

排他锁时,加锁的范围:

记录锁:添加在行索引上的锁
间隙锁:锁定范围是索引记录之间的间隙,针对可重复读以上隔离级别
​临键锁:记录锁+间隙锁

表锁:

​ 意向锁:在获取某行的锁之前,必须要获取表的锁,分为意向共享锁,意向排它锁

​ 自增锁:对自增字段所采用的特殊表级锁

​ 锁模式的含义:

​ IX:意向排它锁

X:锁定记录本身和记录之前的间隙、临键锁

​ S:锁定记录本身和记录之前的间隙、临键锁

X, REC_NOT_GAP:只锁定记录本身、记录锁

​ S,REC_NOT_GAP:只锁定记录本身、记录锁

X,GAP:间隙锁,不锁定记录本身

​ S,GAP:间隙锁,不锁定记录本身

​ X,GAP,INSERT_INTENTION:插入意向锁
以下均为扩展内容:(下面对应的问题是MySQL在什么情况下加什么样的锁
查看MySQL锁的时候需要先开启一个参数:innodb_status_output_locks=on 并使用命令 show engine innodb status\G; 来查看锁的具体情况。难点在于在不同的隔离级别加什么锁,本质在于RR下是加锁最小粒度确保不幻读
在这里插入图片描述
RR级别下增加了间隙锁与临键锁,为了解决的问题是幻读问题

  • RR + 表无显示主键没有索引(隐含字段row_id作为主键)=>临键锁、还锁到正无穷的数据,防止幻读(整个区间加锁)
  • RR+有显示主键(默认主键索引,唯一性)没有索引=> 此时加where条件是主键索引,是行锁,防止修改
  • RR+无显示主键有索引(普通索引,可重复)=>此时加where条件是普通索引,加的是临键锁,间隙锁,防止插入与修改
  • RR+表无显示主键有唯一索引(此索引为聚簇索引)=> 此时加where条件是唯一索引,是行锁,防止修改
  • RR+表有显示主键并有索引 (自行分析)
  • RR+表有显示主键有唯一索引(自行分析)
    我们的数据库隔离级别是RR吗?加锁是为了保证不幻读,有没有主键?有没有唯一键?有没有普通索引?

RC级别下加的都是行锁,为了解决的问题是脏读问题

  • RC + 表无显示主键没有索引(隐含字段row_id作为主键)=>行锁
  • RC+有显示主键(默认主键索引,唯一性)没有索引=> 行锁
  • RC+无显示主键有索引(普通索引,可重复)=>row_id 和聚簇索引都加行锁
  • RC+表无显示主键有唯一索引(此索引为聚簇索引)=> 唯一索引加行锁
  • RC+表有显示主键并有索引 =>行锁
  • RR+表有显示主键有唯一索引=>行锁

14、MySQL中有哪些日志文件,各有什么作用?


15、能详细说一下MVCC的实现原理吗?

MVCC,全称Multi-Version Concurrency Control,即多版本并发控制。MVCC是一种并发控制的方法,一般在数据库管理系统中,实现对数据库的并发访问,在编程语言中实现事务内存。MVCC在MySQL InnoDB中的实现主要是为了提高数据库并发性能,用更好的方式去处理读写冲突,做到即使有读写冲突时,也能做到不加锁,非阻塞并发读

  • 当前读
    ​像select lock in share mode(共享锁), select … for update(加锁读) ; update, insert , delete(排他锁)这些操作都是一种当前读,为什么叫当前读?就是它读取的是记录的最新版本,读取时还要保证其他并发事务不能修改当前记录,会对读取的记录进行加锁

  • 快照读(提高数据库的并发查询能力)
    ​像不加锁的select操作就是快照读,即不加锁的非阻塞读;快照读的前提是隔离级别不是串行级别,串行级别下的快照读会退化成当前读;之所以出现快照读的情况,是基于提高并发性能的考虑,快照读的实现是基于多版本并发控制,即MVCC,可以认为MVCC是行锁的一个变种,但它在很多情况下,避免了加锁操作,降低了开销;既然是基于多版本,即快照读可能读到的并不一定是数据的最新版本,而有可能是之前的历史版本

  • 当前读、快照读、MVCC关系
    MVCC多版本并发控制指的是维持一个数据的多个版本,使得读写操作没有冲突,快照读是MySQL为实现MVCC的一个非阻塞读功能。MVCC模块在MySQL中的具体实现是由三个隐式字段undo日志read view,三个组件来实现的。

  • MVCC解决的问题
    ​ 数据库并发场景有三种,分别为:
    ​ 1、读读:不存在任何问题,也不需要并发控制
    ​ 2、读写:有线程安全问题,可能会造成事务隔离性问题,可能遇到脏读、幻读、不可重复读
    ​ 3、写写:有线程安全问题,可能存在更新丢失问题

    MVCC是一种用来解决读写冲突的无锁并发控制,也就是为事务分配单项增长的时间戳,为每个修改保存一个版本,版本与事务时间戳关联,读操作只读该事务开始前的数据库的快照,所以MVCC可以为数据库解决一下问题:
    ​ 1、在并发读写数据库时,可以做到在读操作时不用阻塞写操作,写操作也不用阻塞读操作,提高了数据库并发读写的性能
    ​ 2、解决脏读、幻读、不可重复读等事务隔离问题,但是不能解决更新丢失问题

  • MVCC实现原理
    ​ mvcc的实现原理主要依赖于记录中的三个隐藏字段,undolog,read view来实现的。

    • 隐藏字段
      ​ 每行记录除了我们自定义的字段外,还有数据库隐式定义的DB_TRX_ID, DB_ROLL_PTR, DB_ROW_ID等字段

      1)DB_TRX_ID
      ​ 6字节,最近修改事务id,记录创建这条记录或者最后一次修改该记录的事务id
      ​ 2)DB_ROLL_PTR
      ​ 7字节,回滚指针,指向这条记录的上一个版本,用于配合undolog,指向上一个旧版本
      ​ 3)DB_ROW_ID
      ​ 6字节,隐藏的主键,如果数据表没有主键,那么innodb会自动生成一个6字节的row_id

      记录如图所示:在这里插入图片描述
      在上图中,DB_ROW_ID是数据库默认为该行记录生成的唯一隐式主键,DB_TRX_ID是当前操作该记录的事务ID,DB_ROLL_PTR是一个回滚指针,用于配合undo日志,指向上一个旧版本

    • undo log
      ​undolog被称之为回滚日志,表示在进行insert,delete,update操作的时候产生的方便回滚的日志。

      当进行insert操作的时候,产生的undolog只在事务回滚的时候需要,并且在事务提交之后可以被立刻丢弃。

      当进行update和delete操作的时候,产生的undolog不仅仅在事务回滚的时候需要,在快照读的时候也需要,所以不能随便删除,只有在快照读或事务回滚不涉及该日志时,对应的日志才会被purge线程统一清除(当数据发生更新和删除操作的时候都只是设置一下老记录的deleted_bit,并不是真正的将过时的记录删除,因为为了节省磁盘空间,innodb有专门的purge线程来清除deleted_bit为true的记录,如果某个记录的deleted_id为true,并且DB_TRX_ID相对于purge线程的read view 可见,那么这条记录一定时可以被清除的)

    • 下面我们来看一下undolog生成的记录链
      1、假设有一个事务编号为1的事务向表中插入一条记录,那么此时行数据的状态为:
      在这里插入图片描述
      ​2、假设有第二个事务编号为2对该记录的name做出修改,改为lisi:
      在事务2修改该行记录数据时,数据库会对该行加排他锁。
      ​然后把该行数据拷贝到undolog中,作为 旧记录,即在undolog中有当前行的拷贝副本。
      ​拷贝完毕后,修改该行name为lisi,并且修改隐藏字段的事务id为当前事务2的id,回滚指针指向拷贝到undolog的副本记录中。
      事务提交后,释放锁:
      在这里插入图片描述
      3、假设有第三个事务编号为3对该记录的age做了修改,改为32
      在事务3修改该行数据的时,数据库会对该行加排他锁。
      然后把该行数据拷贝到undolog中,作为旧纪录,发现该行记录已经有undolog了,那么最新的旧数据作为链表的表头,插在该行记录的undolog最前面。
      修改该行age为32岁,并且修改隐藏字段的事务id为当前事务3的id,回滚指针指向刚刚拷贝的undolog的副本记录。
      事务提交,释放锁
      在这里插入图片描述
      ​从上述的一系列图中,大家可以发现,不同事务或者相同事务的对同一记录的修改,会导致该记录的undolog生成一条记录版本线性表,即链表,undolog的链首就是最新的旧记录,链尾就是最早的旧记录

    • Read View
      上面的流程如果看明白了,那么大家需要再深入理解下read view的概念了。
      ​Read View是事务进行快照读操作的时候生产的读视图,在该事务执行快照读的那一刻,会生成一个数据系统当前的快照,记录并维护系统当前活跃事务的id,事务的id值是递增的。

      其实Read View的最大作用是用来做可见性判断的,也就是说当某个事务在执行快照读的时候,对该记录创建一个Read View的视图,把它当作条件去判断当前事务能够看到哪个版本的数据,有可能读取到的是最新的数据,也有可能读取的是当前行记录的undolog中某个版本的数据

      Read View遵循的可见性算法主要是将要被修改的数据的最新记录中的DB_TRX_ID(当前事务id)取出来,与系统当前其他活跃事务的id去对比,如果DB_TRX_ID跟Read View的属性做了比较,不符合可见性,那么就通过DB_ROLL_PTR回滚指针去取出undolog中的DB_TRX_ID做比较,即遍历链表中的DB_TRX_ID,直到找到满足条件的DB_TRX_ID,这个DB_TRX_ID所在的旧记录就是当前事务能看到的最新老版本数据。

      Read View的可见性规则如下所示:
      首先要知道Read View中的三个全局属性:

      trx_list:一个数值列表,用来维护Read View生成时刻系统正活跃的事务ID(1,2,3)
      up_limit_id:记录trx_list列表中事务ID最小的ID(1)
      low_limit_id:Read View生成时刻系统尚未分配的下一个事务ID,(4)

      具体的比较规则如下:

      1、首先比较DB_TRX_ID < up_limit_id,如果小于,则当前事务能看到DB_TRX_ID所在的记录,如果大于等于进入下一个判断。
      2、接下来判断DB_TRX_ID >= low_limit_id,如果大于等于则代表DB_TRX_ID所在的记录在Read View生成后才出现的,那么对于当前事务肯定不可见,如果小于,则进入下一步判断。
      3、判断DB_TRX_ID是否在活跃事务中,如果在,则代表在Read View生成时刻,这个事务还是活跃状态,还没有commit,修改的数据,当前事务也是看不到,如果不在,则说明这个事务在Read View生成之前就已经开始commit,那么修改的结果是能够看见的。

  • MVCC的整体处理流程

    假设有四个事务同时在执行,如下图所示:

    事务1事务2事务3事务4
    事务开始事务开始事务开始事务开始
    修改且已提交
    进行中快照读进行中

    从上述表格中,我们可以看到,当事务2对某行数据执行了快照读,数据库为该行数据生成一个Read View视图,可以看到事务1和事务3还在活跃状态,事务4在事务2快照读的前一刻提交了更新,所以,在Read View中记录了系统当前活跃事务1,3,维护在一个列表中。同时可以看到up_limit_id的值为1,而low_limit_id为5,如下图所示:
    在这里插入图片描述
    在上述的例子中,只有事务4修改过该行记录,并在事务2进行快照读前,就提交了事务,所以该行当前数据的undolog如下所示:
    在这里插入图片描述
    当事务2在快照读该行记录的是,会拿着该行记录的DB_TRX_ID去跟up_limit_id,lower_limit_id和活跃事务列表进行比较,判读事务2能看到该行记录的版本是哪个。

    具体流程如下:先拿该行记录的事务ID(4)去跟Read View中的up_limit_id相比较,判断是否小于,通过对比发现不小于,所以不符合条件,继续判断4是否大于等于low_limit_id,通过比较发现也不大于,所以不符合条件,判断事务4是否处理trx_list列表中,发现不再次列表中,那么符合可见性条件,所以事务4修改后提交的最新结果对事务2 的快照是可见的,因此,事务2读取到的最新数据记录是事务4所提交的版本,而事务4提交的版本也是全局角度的最新版本。如下图所示:
    在这里插入图片描述
    当上述的内容都看明白了的话,那么大家就应该能够搞清楚这几个核心概念之间的关系了,下面我们讲一个不同的隔离级别下的快照读的不同。

  • RC、RR级别下的InnoDB快照读有什么不同
    因为Read View生成时机的不同,从而造成RC、RR级别下快照读的结果的不同。

    1、在RR级别下的某个事务的对某条记录的第一次快照读会创建一个快照即Read View,将当前系统活跃的其他事务记录起来,此后在调用快照读的时候,还是使用的是同一个Read View,所以只要当前事务在其他事务提交更新之前使用过快照读,那么之后的快照读使用的都是同一个Read View,所以对之后的修改不可见。

    2、在RR级别下,快照读生成Read View时,Read View会记录此时所有其他活动和事务的快照,这些事务的修改对于当前事务都是不可见的,而早于Read View创建的事务所做的修改均是可见。
    3、在RC级别下,事务中,每次快照读都会新生成一个快照和Read View,这就是我们在RC级别下的事务中可以看到别的事务提交的更新的原因。

总结:在RC隔离级别下,是每个快照读都会生成并获取最新的Read View,而在RR隔离级别下,则是同一个事务中的第一个快照读才会创建Read View,之后的快照读获取的都是同一个Read View.


16、MySQL主键为什么不建议为null?


17、InnoDB引擎为什么推荐使用自增ID作为主键?

页分裂、页合并
⾃增ID可以保证每次插⼊时B+索引是从右边扩展的,可以避免B+树和频繁合并和分裂(对⽐使⽤UUID)。如果使⽤字符串主键和随机主键,会使得数据随机插⼊,效率⽐较差。


18、MySQL小表驱动大表原理(in和exists的区别)?


19、MySQL慢查询优化?能详细地描述一下吗?

总结

如何回答MySQL中的面试问题?

  • 当遇到实际场景问题,尽可能的迁移,发挥自己的实际水平
  • 问的理论知识的时候,先想一想表达的逻辑,梳理一下说话的脉络
  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值