MySQL之从MVCC角度来看事务隔离

27 篇文章 0 订阅
4 篇文章 0 订阅

起因

提到事务,相信大家都很熟悉,毕竟这个是与数据库息息相关的。事务的四大特性:原子性,一致性,隔离性,持久性相信也早已深入人心。但是很多人都只是知道这个概念,怎么用(包括我)。恰逢今天学习了相关知识,今天就从MVCC(多版本并发控制技术)来好好深入研究一下,特此做总结一篇。

不同事务之间可能引发的问题以及隔离级别

当多个事务在同时执行的时候,就有可能引发如下三个问题:

  • 脏读:一个事务读取另外一个事务还没有提交的数据叫脏读。
  • 幻读:幻读一般指的是一个事务在操作一片数据,这个时候另外一个事务插入另一条数据,这个时候第一个事务发现怎么自己刚操作的数据又和原来不一样了,就如同出现幻像一般。
  • 不可重复读:不可重复读是指在同一个事务内,两个相同的查询返回了不同的结果。

解决方案:隔离,但是这里存在一把双刃剑,当你隔离程度越大,自然越安全,当达到串行化,安全性最高,但是你隔离得越严实,说明你的效率越低。所以你需要去权衡利弊,找到一个平衡点,为此也引发出了隔离级别的概念。
隔离级别有四:

  • 读未提交:一个事务还没提交时候,它做的变更就能被另一个事务看到。
  • 读提交:一个事务提交后,它做的变更才会被其他事务看到。
  • 可重复读:一个事务在执行过程中看到的数据,总是和事务刚启动的时候看到的数据是一致的,在可重复读隔离级别下,未提交变更对其他事务是不可见的
  • 串行化:顾名思义了,一个一个事务轮着执行,事务不并行,自然是没有问题的。
show variables like 'transaction_isolation'//mysql可以通过以下指令来查看隔离级别,默认是可重复读

他们的对应关系如下图:
在这里插入图片描述
MySQL中,默认的隔离级别是可重复读,从上图可得知,可重复读只能解决脏读和重复读问题,但是不能解决幻读问题,幻读只有串行化才能解决,但是效率太低了。所以这个时候引入了我们今天的主角。MVCC机制,它可以在大多数代替行锁,降低系统开销。

MVCC是什么,能够解决什么问题?

MVCC,中文意思就是多版本控制技术,通过数据行的多个版本管理来控制实现数据库的并发控制,其主要思想就是保存数据的历史版本。主要解决的问题有以下三种:

  1. 解决读写之间的阻塞问题,通过MVCC可以让读写互相不阻塞。
  2. 降低死锁的概率,MVCC采用了乐观锁的方式。
  3. 解决一致性读问题,一致性读也被称为快照读,查询数据库某个时间点的快照时候,只能看到这个时间点之前事务提交更新的结果,对于这个时间点后更新的结果就不能看见。

快照读和当前读

上述中,我们看到解决的问题中提到了快照读,一般伴随这快照读还有当前读的概念,接下来我们来看看这两个概念分别是什么。
快照都读取的是快照数据,一般SQL语句如下

SELECT * FROM t WHERE...

当前读读取的是最新数据,而不是历史版本的数据。一般进行当前读的SQL语句有下

SELECT * FROM t LOCK IN SHARE MODE //加锁
SELECT * FROM t FOR UPDATE
INSERT INTO t VALUES ....
DELETE FROM t WHERE...
UPDATE t SET ...

从上述SQL语句可知,快照读其实就是普通的查询语句,当前读包括了加锁的读取和DML操作。

MVCC在InnoDB中的实现

MVCC的意思是多版本控制,既然它能够进行多版本控制,肯定在哪里有存储跟版本有关的信息。接下来我们就来看看这些有关的信息数据

事务版本号:每开启一个事务,我们都会从数据库获取一个事务ID,这个ID是自增长的,可以用来判断事务出现的时间顺序。

行记录的隐藏列:

  1. db_row_id:隐藏的行id,作用是生成默认的聚簇索引,如果在创建表的时候没有指定聚簇索引,就会默认使用这个隐藏的ID来创建聚簇索引,聚簇索引可以提升数据查找效率
  2. db_trx_id:操作这个数据的事务ID,也就是最后一个对该数据插入或者更新的事务ID。
  3. db_roll_ptr:回滚指针,指向这个记录的Undo log信息

图示:
在这里插入图片描述

上述讲到了一个Undo log信息,那么这个信息是什么呢?这个log是用来存放行记录快照的。图示如下:
在这里插入图片描述
从上图中,可知道回滚指针将数据行的所有快照记录连接起来,每个快照还保持了当时操作的事务id。如果我们想要找到历史快照,就能通过遍历回滚指针的方式进行查找。

Read View是如何工作

在MVCC中,多个事务对同一个行记录进行更新的时候,会产生多个历史快照,这些历史快照都被保存Undo log中。那如果一个事务想要查询自己操作的行记录时候,需要读哪个版本的呢?这个时候Read View就出现了。Read View保存了当前事务开启的时候所有活跃的事务列表,活跃就是还没有提交的意思。说白了这个列表保存的就是当前事务不应该看到的其他事务的ID。
视图的几个重要属性:
trx_ids:活跃的事务ID集合。
low_limit_id:活跃事务的最大事务ID,大于这个ID不可见。
up_limit_id:活跃事务中最小的事务ID,小于这个ID可见。
creator_trx_id:创建这个视图的事务ID。
图示:
在这里插入图片描述
如果当前事务读取某个行记录,这个行记录的ID为trx_id,那么有以下几种情况:

  • 情况1:trx_id小于up_limit_id,说明这个行记录在当前事务创建之前就提交了,对当前事务是可见的。
  • 情况2:trx_id大于low_limit_id,说明操作这个行记录的事务还没开始,这个对当前事务肯定是不可见的。
  • 情况3:up_limit_id<trx_id<low_limit_id,这个时候得去判断trx_id是否还处于活跃状态,如果已经提交,那就可见,否则不可见。

PS:MVCC通过Undo log和Read View进行数据读取,Undo log+Read View进行数据读取,Undo保存历史版本,Read View判断每个读取语句有资格读取版本链中的哪个记录。

说了这么多MVCC的知识,接下来,我们来看看MVCC和事务隔离之间的关系
读未提交
该隔离级别下 不会使用MVCC,执行select,获取B+树上的最新记录

读提交
该隔离级别下,每次使用查询语句,就会重新获取一次read view,因为read view不同,所以就有可能引发不可重复读和幻读的情况

可重复读
事务只会在第一次select的时候获取read view,接下来的过程中就是复用这个read view。所以就不会出现不可重复读问题

串行化
该隔离级别下不会使用MVCC,如果使用普通的select,会在语句后面加上lock in share mode。变成一致性的锁定读。

InnoDB是如何解决幻读的
InnoDB是通过Next-Key+MVCC来解决幻读的,出现幻读的原因是读已提交的情况下,InnoDB只采用记录锁。
InnoDB三种行锁的方式:
1、记录锁:针对单个行加锁
2、间隙锁,可以锁住一个范围(索引间的间隙)
3、Next-Key锁,也是锁定一个范围,同时锁定记录本身,就是相当于间隙锁加记录锁。

关于数据库锁方面,后续我会专门写一篇来总结,大家觉得有兴趣也可以关注下。
如果文章有错误,希望大家能也能指出。

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值