Mysql-(三)-mysql中的事务与MVCC介绍

事务的定义 以及 具备的四大特性概述:


事务是数据库管理系统(DBMS)执行过程中的一个逻辑单位,由一个有限的数据库操作序列构成。

事务的四大特性

原子性: Atomicity,要么都是成功,要么都是失败。

原子性,在 InnoDB 里面是通过 undo log 来实现的,它记录了数据修改之前的值(逻辑日志),一旦发生异常,就可以用 undo log 来实现回滚操作。

一致性 :Consistency,指的是数据库的完整性约束没有被破坏。

隔离性 :,Isolation,我们有了事务的定义以后,在数据库里面会有很多的。

事务同时去操作我们的同一张表或者同一行数据,必然会产生一些并发或者干扰的操作,
那么我们对隔离性的定义,就是这些很多个的事务,对表或者行的并发操作,应该是透
明的,互相不干扰的。通过这种方式,我们最终也是保证业务数据的一致性。

持久性 : durability。我们对数据库的任意的操作,增删改,只要事务提交成功,那么结果就是永久性的,不可能因为我们系统宕机或者重启了数据库的服务 器,它又恢复到原来的状态了。这个就是事务的持久性。

持久性是通过 redo log 来实现的,我们操作数据的时候,会先写到内存的 buffer pool 里面,同时记录 redo log,如果在刷盘之前出现异常,在重启后就可以读取 redo log 的内容,写入到磁盘,保证数据的持久性。

实际上还有一个双写缓冲的机制
因为存储引擎的页和操作系统的页大小不一致。
一个存储引擎 page 的数据要写 4次,如果中间发生异常,或造成页数据的不可用。
所以,必须把页的数据备份起来,这 个就是双写缓冲(double write buffer)。

原子性,隔离性,持久性,最后都是为了实现一致性。


SQL92标准

  1. 第一个隔离级别叫做:Read Uncommitted(未提交读),一个事务可以读取到其 他事务未提交的数据,会出现脏读,所以叫做 RU,它没有解决任何的问题。
  2. 第二个隔离级别叫做:Read Committed(已提交读),也就是一个事务只能读取 到其他事务已提交的数据,不能读取到其他事务未提交的数据,它解决了脏读的问题, 但是会出现不可重复读的问题。
  3. 第三个隔离级别叫做:Repeatable Read (可重复读),它解决了不可重复读的问题, 也就是在同一个事务里面多次读取同样的数据结果是一样的,但是在这个级别下,没有定义解决幻读的问题。
  4. 最后一个就是:Serializable(串行化),在这个隔离级别里面,所有的事务都是串 行执行的,也就是对数据的操作需要排队,已经不存在事务的并发操作了,所以它解决 了所有的问题。

在这里插入图片描述

两大实现方案 :

LBCC

第一种,读取数据的时候,锁定我要操作的数据,不允许其他的事务修改就行了。
这种方案我们叫做基于锁的并发控制 Lock Based Concurrency Control(LBCC)。

MVCC

在修改数据的时候给它建立一个备份或者叫快照,后面再来读取这个快照就行了。
这种方案我们叫做多版本的并发控制 Multi Version Concurrency Control(MVCC)。

MVCC 的核心思想是: 我可以查到在我这个事务开始之前已经存在的已提交的数据,即使它在后面被修改或者删除了。在我这个事务之后新增的数据,我是查不到的。

当前行创建时的版本号和删除时的版本号(可能为空,其实还有一列称为回滚指针,用于事务回滚,不在本文范畴)。这里的版本号并不是实际的时间值,而是系统版本号。每开始新的事务,系统版本号都会自动递增。事务开始时刻的系统版本号会作为事务的版本号,用来和查询每行记录的版本号进行比较。


[MVCC 分析总结]

原文,下面摘录一些重点概念。

多版本控制(Multiversion Concurrency Control): 指的是一种提高并发的技术。最早的数据库系统,只有读读之间可以并发,读写,写读,写写都要阻塞。引入多版本之后,只有写写之间相互阻塞,其他三种操作都可以并行,这样大幅度提高了InnoDB的并发度。
在内部实现中,InnoDB通过undo log保存每条数据的多个版本,并且能够找回数据历史版本提供给用户读,每个事务读到的数据版本可能是不一样的。在同一个事务中,用户只能看到该事务创建快照之前已经提交的修改和该事务本身做的修改

MVCC在 Read CommittedRepeatable Read两个隔离级别下工作。

MySQL的InnoDB存储引擎默认事务隔离级别是Repeatable Read,是通过 "行级锁+MVCC"一起实现的。
正常读的时候不加锁,写的时候加锁。

1.)MVCC 的实现依赖:
  1. 隐藏字段、
  2. Read View、
  3. Undo log。
1、隐藏字段
    InnoDB存储引擎在每行数据的后面添加了三个隐藏字段:
  1. DB_TRX_ID(6字节):表示最近一次对本记录行作修改(insert | update)的事务ID。至于delete操作,InnoDB认为是一个update操作,不过会更新一个另外的删除位,将行表示为deleted。并非真正删除。

  2. DB_ROLL_PTR(7字节):回滚指针,指向当前记录行的undo log信息

  3. DB_ROW_ID(6字节):随着新行插入而单调递增的行ID。理解:当表没有主键或唯一非空索引时,innodb就会使用这个行ID自动产生聚簇索引。如果表有主键或唯一非空索引,聚簇索引就不会包含这个行ID了。

这个DB_ROW_ID跟MVCC关系不大。

2、Read View 结构(重点)

其实Read View(读视图),跟快照、snapshot是一个概念。

Read View主要是用来做可见性判断的, 里面保存了“对本事务不可见的其他活跃事务”,其中会有参数标明当前事务id、下一个事务id等。

3、Undo log

Undo log中存储的是老版本数据,当一个事务需要读取记录行时,如果当前记录行不可见,可以顺着undo log链找到满足其可见性条件的记录行版本。

大多数对数据的变更操作包括 insert/update/delete,在InnoDB里,undo log分为如下两类:

1. insert undo log : 事务对insert新记录时产生的undo log, 只在事务回滚时需要, 并且在事务提交后就可以立即丢弃。
2. update undo log : 事务对记录进行delete和update操作时产生的undo log,不仅在事务回滚时需要,快照读也需要,只有当数据库所使用的快照中不涉及该日志记录,对应的回滚日志才会被purge线程删除。


2.)MVCC 的视野下插入:

我们构建一条初始化数据如下:
在这里插入图片描述
对上述初始数据,出现了新事务A(事务ID为2)。 要对该记录的Honor做出了修改,将Honor改为"fmvp":

  1. 事务A先对该行加排它锁。
  2. 然后把该行数据拷贝到undo log中,作为旧版本。
  3. 拷贝完毕后,修改该行的Honor为"fmvp",并且修改DB_TRX_ID为2(事务A的ID), 回滚指针指向拷贝到undo log的旧版本。(然后还会将修改后的最新数据写入redo log)

在这里插入图片描述

  1. 事务提交,释放排他锁

我们在重复下上述这个流程,假设又来了个新事务,事务B(事务ID为3)修改同一个记录行,将Name修改为"iguodala",流程跟上一步一致,如下。

  1. 事务B先对该行加排它锁
  2. 然后把该行数据拷贝到undo log中,作为旧版本
  3. 拷贝完毕后,修改该行Name为"iguodala",并且修改DB_TRX_ID为3(事务B的ID), 回滚指针指向拷贝到undo log最新的旧版本。

在这里插入图片描述

  1. 事务提交,释放排他锁

发现了什么?

不同事务或者相同事务的对同一记录行的修改,会使该记录行的undo log成为一条链表,undo log的链首就是最新的旧记录,链尾就是最早的旧记录。

3.)MVCC 的可见性:

innodb中,创建一个新事务后,执行第一个select语句的时候,innodb会创建一个快照(read view),快照中会保存系统当前不应该被本事务看到的其他活跃事务id列表(即trx_ids)。

当用户在这个事务中要读取某个记录行的时候,innodb会将该记录行的DB_TRX_ID与该Read View中的一些变量进行比较,判断是否满足可见性条件。

假设当前事务要读取某一个记录行,设该记录行如下:

  • DB_TRX_ID(即最新修改该行的事务ID)为trx_id
  • Read View的活跃事务列表trx_ids中最早的事务ID为up_limit_id
  • 将在生成这个Read Vew时系统出现过的最大的事务ID+1记为low_limit_id(即还未分配的事务ID)

具体的比较算法如下:

  1. 如果 trx_id < up_limit_id, 那么表明“最新修改该行的事务”在“当前事务”创建快照之前就提交了,所以该记录行的值对当前事务是可见的。跳到步骤5。

  2. 如果 trx_id >= low_limit_id, 那么表明“最新修改该行的事务”在“当前事务”创建快照之后才修改该行,所以该记录行的值对当前事务不可见。跳到步骤4。

  3. 如果 up_limit_id <= trx_id < low_limit_id, 表明“最新修改该行的事务”在“当前事务”创建快照的时候可能处于“活动状态”或者“已提交状态”;所以就要对活跃事务列表trx_ids进行查找(源码中是用的二分查找,因为是有序的):

    3.1 (1) 如果在活跃事务列表trx_ids中能找到 id 为 trx_id 的事务,表明①在“当前事务”创建快照前,“该记录行的值”被“id为trx_id的事务”修改了,但没有提交;或者②在“当前事务”创建快照后,“该记录行的值”被“id为trx_id的事务”修改了(不管有无提交);这些情况下,这个记录行的值对当前事务都是不可见的,跳到步骤4;

    3.2 (2)在活跃事务列表中找不到,则表明“id为trx_id的事务”在修改“该记录行的值”后,在“当前事务”创建快照前就已经提交了,所以记录行对当前事务可见,跳到步骤5。

  4. 在该记录行的 DB_ROLL_PTR 指针所指向的undo log回滚段中,取出最新的的旧事务号DB_TRX_ID, 将它赋给trx_id,然后跳到步骤1重新开始判断。

  5. 将该可见行的值返回。

3.1)当前读和快照读:

快照读(snapshot read):普通的 select 语句(不包括 select … lock in share mode, select … for update)

当前读(current read)select … lock in share mode,select … for update,insert,update,delete 语句(这些语句获取的是数据库中的最新数据,官方文档:14.7.2.4 Locking Reads )

只靠 MVCC 实现RR隔离级别,可以保证可重复读,还能防止部分幻读,但并不是完全防止。

当事务A是快照读时:

比如事务A开始后,执行普通select语句,创建了快照;之后事务B执行insert语句;然后事务A再执行普通select语句,得到的还是之前B没有insert过的数据,因为这时候A读的数据是符合快照可见性条件的数据。

当事务A是快照读时:

事务A是当前读,每次语句执行的时候都是获取的最新数据。也就是说,在只有MVCC时,
1.A先执行 select … where nid between 1 and 10 … for update;
2.事务B再执行 insert … nid = 5 …;
3. A 再执行 select … where nid between 1 and 10 … for update
当执行完第三条语句时,会发现多了一条B insert进去的记录,这就产生幻读了。所以单独靠MVCC并不能完全防止幻读。

因此,InnoDB在实现RR隔离级别时,不仅使用了MVCC,还会对“当前读语句”读取的记录行加记录锁(record lock)和间隙锁(gap lock),禁止其他事务在间隙间插入记录行,来防止幻读,在锁的章节会提到这一点。

3.2)RR和RC的Read View产生区别:

①在innodb中的Repeatable Read级别, 只有事务在begin之后,执行第一条select(读操作)时, 才会创建一个快照(read view),将当前系统中活跃的其他事务记录起来;并且事务之后都是使用的这个快照,不会重新创建,直到事务结束。

②在innodb中的Read Committed级别, 事务在begin之后,执行每条select(读操作)语句时,快照会被重置,即会重新创建一个快照(read view)。

原来区别是在这

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值