DB优化MySQLJava基础

MySQL索引、事务、锁、MVCC简述

2021-10-19  本文已影响0人  小驴小驴

目录

MySQL索引、事务、锁、MVCC简述
一、索引
  1.1 执行计划 Explain
  1.2 索引结构
    1.2.1 Hash
    1.2.2 二叉搜索树
    1.2.3 平衡二叉搜索树(AVL)
    1.2.4 多叉平衡搜索树
      1.2.4.1 B-Tree
      1.2.4.2 B+Tree
    1.2.5 B-Tree与B+ Tree的区别
  1.3 Myisum与InnoDB的区别
    1.3.1 Myisum
    1.3.2 InnoDB
  1.4 名词解释
二、事务
  2.1 事务的定义
  2.2 不同事物隔离级别效果
  2.2 事务中的隔离性实现
  2.3 锁机制
    2.3.1 常见的锁
    2.3.2 锁的分类
      2.3.2.1 锁升级情况
    2.3.4 Innodb下行级锁原理
  2.4 MVCC
    2.4.1 快照读、当前读
      2.4.1.1 快照读
      2.4.1.2 当前读
    2.4.2 MVCC的实现原理
      2.4.2.1 三要素
      2.4.2.2 具体流程
三、常见面试题
  3.1 不可重复读、幻读的主要区别
  3.2 间隙锁与临键锁为什么能避免幻读
  3.3 MVCC与锁的关系
  3.4 MVCC 一定可以解决幻读吗
  3.5 解决幻读的终极杀器

一、索引

1.1 执行计划 Explain

可以查看之前的一篇博客,这里简单会回忆一下

https://www.jianshu.com/p/3ea49fd0067e

Explain可以查看一条语句的执行计划,显示出比较重要的几个字段:

1.2 索引结构

1.2.1 Hash

特点:数据的存储与取数都是通过计算Hash值去定位

优势:适合等值查询

弊端:

1.2.2 二叉搜索树

特点:是一颗树且左子树的Value小于根且根小于右子树。

优势:查询效率要高于Hash方式

弊端:在某些情况下,如表记录的ID使用自增主键,这样的情况下,二叉搜索树会退化成单链表,导致树的深度过深

1.2.3 平衡二叉搜索树(AVL)

特点:为了解决二叉搜索树的深度可能会过深,平衡二叉搜索树(AVL树)增加了一些约束,如:左右子树的高度差不能大于1,尽可能的让二叉搜索树变得平衡。常见的平衡二叉搜索树有:红黑树、数堆

弊端:虽然平衡二叉搜索树解决了二叉搜索树有可能退化成单链表的情况,但是树毕竟属于二叉,因此树的深度还是会比较深。

1.2.4 多叉平衡搜索树
1.2.4.1 B-Tree

特点:如果采用平衡二叉搜索树,因为属于二叉树,大量的数据还是会导致树的深度过深,因此树中的一个节点就不能只有2个子节点,应该允许M个子节点(M>2),B-Tree就是为了解决这个问题,B- Tree的结构如下:


优势:一定程度上解决了树的深度

弊端:因为所有的节点都会存储行记录,因此每一个节点可以表示的状态较少,导致树的深度仍然比较高。

1.2.4.2 B+Tree

特点:同B-Tree的结构相似,但是非叶子节点除了存储关键字之外不会存储行记录。除此之外,所有的叶子节点除了在同一层的状态之外,还会使用指针进行连接,形成叶子节点的单链表。

优势:

1.2.5 B-Tree与B+ Tree的区别

1.3 Myisum与InnoDB的区别

1.3.1 Myisum

支持的索引:主键索引、唯一索引、普通索引、复合索引、全文索引

这里顺带提一下MyIsum的文件结构:

一个数据表会有三种类型文件:

1.3.2 InnoDB

支持的索引:主键索引、唯一索引、普通索引、复合索引、全文索引(高版本)

InnoDB的文件结构:

1.4 名词解释

在InnoDB引擎下,有多少个索引就有多少个.idb后缀的索引树文件

二、事务

2.1 事务的定义

说到事务,大家都会想到ACID,但ACID其实本身与事务的定义并没有关系

事务,其实是值一组操作,这一组操作包含了多个操作。事务的概念可以应用于任意一款存储中间件,比如MySQL、Redis,甚至Kafka中也有这样的概念。

但是什么样的事务称为好的事务呢?我们常用转账案例去解释什么是好的事务。

好的事务是能够满足ACID的四个特性,我们就称之为该事务是好的事务,是完美的事务。

其中ACID中,C是最重要的目标,其它三个都是为了满足C的特性。

对于事务中,隔离性也非常的重要,隔离性决定着不同事务进行并发的时候,事务之间的数据隔离强度的问题,隔离性分为四个级别,读未提交是隔离性最弱的一种情况,串行读是隔离强度最强的,串行读完全保证了在事务并发的情况下,数据一定是安全的、一致的,但是因为每一步操作都会加锁,导致并发情况下的效率低下。

2.2 不同事物隔离级别效果

事务进行并发过程中,在未经过特殊处理的情况下势必会产生脏读、不可重复读、幻读,而不同强度的事务隔离级别能够解决的问题自然也不同

读未提交:脏读、不可重复读、幻读

读已提交:不可重复读、幻读

可重复读:幻读

串行化读:解决了所有的问题

上述文字对应关系说明:左侧代表不同的事务隔离级别,右侧代表这种隔离级别下仍然会有很多问题。

2.2 事务中的隔离性实现

其实ACID的提出,包括ACID中的四种强度的隔离性都是由ISO提出的标准,而这些也只是标准而已,具体的实现还是要一句不同的数据库厂商。

对于MySQL而言,MySQL是通过如下两种方式的配合去实现事务的隔离性:

这两者实现方式是相辅相成的,MVCC更侧重于DQL语句的事务隔离,而锁机制不论是DQL还是DML都能够有效隔离。

但如果所有语句都需要使用锁机制的话,这也会大大减少了事务的并发性,基于这样的想法,就有了MVCC机制。而锁机制与MVCC机制并非通过加锁来保证事务隔离型。

两者的关系就类似于Java中的悲观锁于CAS之间的关系。

在下文中,会具体阐述锁于InnoDB下的MVCC机制。

2.3 锁机制

通过前文的分析,锁是实现事务隔离性的一种重要手段。而锁的分类很多,根据锁的作用范围又可以分为库锁、表锁、临键锁、间隙锁、行锁,而根据锁是否具有独占、排他的性质,又分为共享锁、排他锁(独占锁)

2.3.1 常见的锁
2.3.2 锁的分类

从粒度上来分:行锁(只有Innodb才有)、表锁、数据库锁

从锁操作上分:读锁、写锁

从实现方式上分:乐观锁、悲观锁

2.3.2.1 锁升级情况

在工作中我们虽然不会去写lock关键字,但是很多时候会无意上升为表锁,

如DML语句,因为DML默认的话就会加行级排他锁,而刚好检索的字段不是索引列,就自动上升成为表锁

  1. 对未加索引的字段作为检索的字段进行上锁操作(select 手动指定锁、或者DML自动加锁)

有一张tt表,tt中的name字段不是索引

如:select * from tt where name = 'zcy'(不会有锁,不论是行锁还是表锁)、

select * from tt where name = 'zcy' lock in share mode (会由行级共享锁上升为表锁)

select * from tt where name = 'zcy' for update (会由行级排他锁上升为表锁)

update tt set age = 1 where name = 'zcy' (会由行级排他锁上升为表锁)

delete from tt where name = 'zcy' (会由行级排他锁上升为表锁)

insert into tt values ('zcy', 22)(会由行级排他锁上升为表锁)

  1. 加锁的语句索引失效
2.3.4 Innodb下行级锁原理
  1. 对主键(聚簇索引)记录加行锁:直接锁定索引记录中某个叶子节点即可

  2. 对于唯一键记录加锁:要锁定该非聚簇索引叶子节点,还要根据叶子节点中聚簇索引的主键值去锁定聚簇索引中对应的叶子节点,以避免修改其它非当前索引的字段(回想一下索引覆盖的概念)

  3. 非唯一键加锁:会升级为表锁

解决疑惑:

上文说事物隔离级别是通过锁或者MVCC两种实现方式去完成的,

我个人觉得这句话有问题,我觉得这两个是相辅相成的去完成的,

比如,事务隔离级别中,幻读是通过间隙锁/临键锁区避免幻读的(Innodb下),这种其实就是通过加锁机制保证不同事务之间的数据隔离性,而MVCC是侧重于而且是快照读,因此所说:数据的DML是通过加锁方式避免幻读,真正在读取过程中(快照读方式)是通过MVCC的机制去避免幻读,所以锁是侧重DML,让事务写形成一种带阻塞状态,一个事务操作,另一个事务就无法操作,自然不会有幻读了,而MVCC是侧重于读,因为虽然你已经有了加锁机制保证了避免幻读,但是我们一般写select的时候,并不会主动加锁,因此读的操作并不会自动纳入到锁机制当中去完成幻读,因此就需要对这些我们大多数常用的快照读作特殊处理,才有了MVCC。

2.4 MVCC

根据上文可知,MVCC是InnoDB引擎下的一款事务隔离级别的具体实现方式,因为MVCC并不需要加锁读因此其吞吐量要优于锁机制,MVCC中文意思为:多版本控制协议。

MVCC工作在InnoDB引擎下的RC、RR两个事务隔离级别中

2.4.1 快照读、当前读

在具体了解锁与MVCC之前,最好了解一下什么是快照读、什么是当前读。

2.4.1.1 快照读

快照读工作在MVCC机制下。

指的是读取快照中的数据,而并不一定是当前最新的数据,常见的不加锁的DQL都是快照读

但有一点需要注意,不同事务隔离级别生成快照读的时机不一样,因为快照读主要是通过MVCC去实现的,因此快照读主要研究在RC、RR两种事务隔离级别下,快照读的生成时机。

除此之外,如果事务中使用了一次当前读,那么快照也会跟着被重建。

2.4.1.2 当前读

当前读是利用锁机制去实现的,保证每次读取到的一定是最新值。

指的是通过加锁方式,让DQL语句在检索中始终读取记录表中的最新数据。

常见的加锁DQL与DML都属于当前读。

2.4.2 MVCC的实现原理

MVCC的实现原理主要通过三个要素,分别是:行记录隐藏字段、Undolog历史版本链、ReadView

2.4.2.1 三要素

而Undolog日志就作为MVCC历史数据提取的数据来源

ReadView就是快照读SQL执行时MVCC从UndoLog中提取数据的依据

而在RC、RR中,ReadView生成的时机不一样:

  • RC

    每次快照读都会生成一个ReadView

  • RR

    只有在第一次进行快照读的时候才会生成ReadView

    除此之外,如果事务中使用了一次当前读,那么快照也会跟着被重建。

也正是因为生成时机不一样,因此才会有了RC、RR的不同,即:RC未能解决不可重复读的问题(就是因为每次进行快照读,ReadView都会重新生成,导致根据MVCC规则,从Undolog都能拿到新一点的数据,而RR就只能拿到旧数据),而RR解决了不可重复读就和他的ReadView总是复用第一次生成的有关, 1⃣️在Undolog不变的情况下,2⃣️ReadView相同,3⃣️判断规则相同,那么读取到的Undolog数据肯定一致。

组成部分:

m_ids:当前活跃的事务编号集合

min_trx_id:最小活跃事务编号

max_trx:id:预分配事务编号,当前最大事务编号+1

creator_trx_id:ReadView创建者的事务编号

2.4.2.2 具体流程

在理解了MVCC三要素之后,这里通过一个小案例去说明MVCC的具体工作流程

先查看以下案例:

前提:事务隔离级别RC情况下。 这是个非常重要的前提,因为RC与RR中MVCC生成ReadView的时机不相同

一共有两个DQL语句,而根据ReadBView的生成规则,可以分别写出右侧两个SelectReadView。

MVCC工作流程总结:

其实也非常容易理解:

  1. 先判断当前查询是不是属于当前DML事务,如果DML、DQL发生在一个事务,并且前后两句话中没有额外的事务操作,那么这条数据就是可读的;
  2. 如果undolog当前版本数据事务id小于最小活跃ID,则表明上一次操作该条记录的事务早已提交,则当前DQL可以进行读取;
  3. 如果undolog当前版本数据事务id大于ReadView中最大活跃事务ID,则说明当前undolog数据是在当前DQL开始事务之后才开启的,属于未来性数据,这样的undolog数据不允许访问;
  4. 如果undolog当前版本属于ReadView中活跃事务区间内,并且不属于ReadView的活跃区间,则说明Undolog数据的上一次操作事务已经提交,则可以读取。

三、常见面试题

3.1 不可重复读、幻读的主要区别:

不可重复读主要针对:Update操作;

幻读主要针对:Insert、Delete操作,即两次相同的查询,数据的总量发生了变化

3.2 间隙锁与临键锁为什么能避免幻读

间隙锁与临键锁的工作原理一样,就是锁住索引关键字的前后两半部分区间,使得整个区间无法进行其它操作,以此来避免幻读;

这个地方你可能会有疑问:为什么只需要锁定索引关键字前后两个区间,而不是锁住整张表。

看下面的案例:

结论:首先前提是需要认知到叶子节点会形成一个单链表,因此在DML或者DQL当前读方式操作某个节点的时候,间隙锁或临键锁会锁住记录前后两个区间,如果对于普通索引这样的关键字是被允许关键字重复的,因此如果另一个事务插入一个相关关键字的记录,那么反应到B+树的结构来说,其必然会落到前后两个区间之中;而在一开始就使用锁机制锁住了前后两个区间,这就导致insert或者delete语句会被阻塞直到超时或者案例中的事务A提交,这样一来就可以避免幻读。

3.3 MVCC与锁的关系

相辅相成的关系,MVCC可以看成是乐观锁,锁是悲观锁,MVCC的效率更高

3.4 MVCC 一定可以解决幻读吗

不可以!

MVCC避免幻读的原理与3.2 间隙锁与临键锁为什么能避免幻读有着极大的不同,这是因为MVCC使用快照方式,去避免了幻读的出现,但是!在MVCC的机制下如果前后两次快照读之间有一次当前读,当前读必然导致了重建ReadView,这就会使得前后两次查询的快照不同,而这之间恰巧有其它事务进行了Insert或者Delete操作,那么依然会导致幻读的出现。

3.5 解决幻读的终极杀器

在分析了3.2 间隙锁与临键锁为什么能避免幻读3.4 MVCC 一定可以解决幻读吗两个面试题之后,可以发现只有通过间隙锁、临键锁的方式才能更加安全的避免幻读的出现。

参考:

注:文章文字较多,编写与整理的过程稍显仓促,若有错误,欢迎纠错。

上一篇下一篇

猜你喜欢

热点阅读