MySQL事务——事务隔离界别,MVCC

MySQL事务

事务能够保证一系列操作要么都生效,要么都不生效.

事务隔离级别

事务的特性

原子性:一个事务中的所有操作,要么全部完成,要不一个都不完成

一致性:事务操作前和后,数据满足完整性约束.不会凭空多出来一个数据或少一个数据.

持久性:事务中的操作对数据的影响是持久的,不会随着断电而失效

隔离性:数据库允许多个事务并发执行,隔离性可以防止事务并发执行导致数据的不一致现象发生

InnoDB引擎保证事务特性的方式

持久性——redo log(重做日志)

原子性——undo log(回滚日志)

隔离性——MVCC(多版本并发控制)或锁机制

一致性——持久性+原子性+隔离性

并发事务引发的问题

  1. 脏读:一个事务读到了另一个未提交事务修改过程中的数据.

    例如:数据a=100,事务A将数据a修改为了200,事务B读取了事务A修改后的结果.事务A发生了回滚

  2. 不可重复读:一个事务内多次读取同一个数据,发现前后读取的内容不一致

    例如:数据a=100,事务A读取了a的值为100,事务B修改a的值为200并提交事务.事务A再次读取a的值为200,前后两次读取到的结果不同

  3. 幻读:一个事务内多次查询某个条件下的记录数量,前后查询到的数量不一致.

事务的隔离级别

  1. 读未提交(read uncommitted):一个事务还没提交时,它做的变更可以被其他事务读取到
  2. 读已提交(read committed):一个事务提交之后,它做的变更才可以被其他事务读取到
  3. 可重复读(repeatable read):一个事务执行过程中看到的数据,和该事务启动时看到的数据保持一致.MySQL InnoDB默认的隔离级别是repeatable read
  4. 串行化(serializable):会对记录加上读写锁,同一时刻只能有一个事务对记录进行读写操作(将并发执行变成单例执行)
读未提交读已提交可重复读串行化
脏读×
不可重复读××
幻读×××

在解决幻读时,一般不是将隔离级别设置成串行化,因为那样执行效率过低.而是在可重复读隔离级别下通过next-key lock锁(行锁和间隙锁的组合)来锁住记录之间的间隙和记录本身,防止其他事务在这个记录之间插入新的记录

隔离级别的实现方式

读已提交和可重复读都是通过Read View来实现的.只是实现时机不同.读已提交是在执行每个语句之前重新生成一个Read View,而可重复读是在启动事务时生成一个Read View,然后整个事务期间都用这个Read View

启动事务并不完全等同于开启事务

开启事务有两种命令

  1. begin/start transaction:开启事务并不是启动事务,只有在执行CRUD时才会启动事务
  2. begin transaction with consistent snapshot:直接启动事务

串行化:给数据加读写锁

Read View在MVCC中的工作过程

  1. Read View的四个字段

在这里插入图片描述

creator_trx_id:创建该Read View的事务的事务id

m_ids:在创建Read View时,当前数据库中活跃且未提交的事务id列表.活跃指的是启动了但没有提交的事务.

min_trx_id:m_ids的最小值,也就是当前活跃的id最小的事务.

max_trx_id:创建Read View时当前数据库应该给下一个事务的id值,也就是m_ids的最大值+1

  1. 主键索引中两个隐藏列

    trx_id

    当一个事务对某条主键索引记录进行修改时,会将该事务的id存储到trx_id中

    trx_id可以分成三部分:已提交事务,已启动但未提交的事务,未启动的事务

    在这里插入图片描述

    事务可见性分析:

    首先事务本身对数据的修改是可见的

    当修改记录的事务id小于min_trx_id时,说明事务是在创建Read View之前就提交的,所以对当前事务是可见的

    当修改记录的事务id在[min_trx_id,max_trx_id)范围内时,需要判断该事务id是否在m_ids中

    如果在m_ids中,说明修改记录的事务还没有提交,所以对当前事务不可见

    如果不在m_ids中,说明修改记录的事务已经被提交,所以对当前事务可见

    当修改记录的事务id大于等于max_trx_id时,说明修改记录的事务还没有生成,所以对当前事务不可见

    roll_pointer

    当某条主键索引记录发生改变时,原来的记录会被写入到undo日志中,roll_pointer是一个指针,会指向undo日志中的每一个旧版本记录.

可重复读的工作过程

可重复读是在事务启动时创建Read View,在整个事务期间都用一个Read View.

可重复读的工作过程:

假设数据库中有一条数据a[value:100,trx_id:20]

事务A启动[creator_trx_id:21,m_ids:21,min_trx_id:21,max_trx_id:22]

事务B启动[creator_trx_id:22,m_ids:21,22,min_trx_id:21,max_trx_id:23]

事务B首先读取数据a的值,发现a的trx_id小于min_trx_id,所以直接读取a的值为100.

然后事务A修改数据a的值为200,此时a的trx_id变为21,并将旧版本的数据存储到undo log中,事务B再次读取数据a时发现trx_id=min_trx_id,而且a的trx_id在事务B的m_ids中,所以不读取该版本的a,而是依靠roll_pointer读取undo日志中的旧版本的数据a,当校验了旧版本的数据a的trx_id满足要求时,读取该a的值为100.

事务A提交事务,事务B再次读取a的值时,和之前一样,会读取undo log中a的值,所以还是100

读提交的工作过程

读提交是在每次读取数据时创建1个Read View

与可重复读的例子一样

假设数据库中有一条数据a[value:100,trx_id:20]

事务A启动[creator_trx_id:21,m_ids:21,min_trx_id:21,max_trx_id:22]

事务B启动[creator_trx_id:22,m_ids:21,22,min_trx_id:21,max_trx_id:23]

事务B首先读取数据a的值,发现a的trx_id小于min_trx_id,所以直接读取a的值为100.

然后事务A修改数据a的值为200,此时a的trx_id变为21,并将旧版本的数据存储到undo log中,事务B再次读取数据a时会创建一个新的Read View[reator_trx_id:22,m_ids:21,22,min_trx_id:21,max_trx_id:23],然后读取时发现trx_id=min_trx_id,而且a的trx_id在事务B的m_ids中,所以不读取该版本的a,而是依靠roll_pointer读取undo日志中的旧版本的数据a,当校验了旧版本的数据a的trx_id满足要求时,读取该a的值为100.

幻读的解决

首先幻读是基于当前读的前提下才会出现的一种问题.如果只是普通的select查询,那么在可重复读的隔离级别下,读取属于[快照读],读取到的数据都是一致的,不会出现幻读问题.

只有是当前读才会出现幻读,当前读比如说在对数据进行更新之前一定要拿到表中的最新数据或者select…for update(当前读)

InnoDB为了解决可重复读级别下基于当前读引发的幻读的问题,引出了next-key锁,即记录锁和间隙锁的组合.

记录锁:锁的是记录本身

间隙锁:锁的是两个值之间的空隙,防止其他事务在这个空隙间插入新的数据,从而避免幻读

当事务想要插入某条数据到表中时会因为间隙锁而阻塞,当事务想要删除某条数据或更新某条数据时会因为记录锁阻塞

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

囚蕤

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值