读mysql45讲-事务的视图

本文详细解释了InnoDB中可重复读隔离级别下事务如何保证数据一致性,涉及事务视图、行锁、活跃事务数组和事务版本控制。重点讨论了事务A在执行过程中,如何处理其他事务更新对它的不可见性以及事务B对数据的影响。

可重复读隔离级别

在可重复读隔离级别的事务A的执行过程中,看到的数据都是一致的,即使是其他事务B修改了数据,在事务A中看到的数据都是一摸一样的,和启动事务A开始的数据保持一致。

这里指的数据一致并不是说是事务A启动之前看到的数据是1000,然后在事务A的执行过程中看到的这个数据一直就是1000,而是说在事务A启动开始的那一刻到提交事务的那一刻,整个事务A的执行过程中看到的这个数据的值都是一样的,可能一直就是1000,也可能就一直都是2000(其它事务的参与),但是数值是不会有不一致的情况。

在行锁中有提到,前事务拥有了行锁,后事务必须等待前事务提交完,才能获得行锁。在等待的过程中,前事务如果更改了数据行的值,那后事务看到的数据行的值是什么了?

begin/start transaction命令开启事务并不是就是一个事务开启的起点,而是需要等到真正执行sql的时候才是开启事务的起点;

start transaction with consistent snapshot命令就是立刻启动这个事务。

事务C中没有显式的begin/commit,但是正常情况下autocommit=1,事务C也就是执行完就提交的意思。

可重复读隔离级别事务在开始的时候,会创建一个视图,这个事务中查看到的数据都是从这个视图中看到的,从而保证事务中看到的数据来源是一致的;而这个视图中的数据相当于对事务开启的那一刻的整个库的数据拍了个快照。

但是整个快照并不是把整个数据库的数据进行快照,如果一个数据库容量有100G,那创建一个事务就需要100G的快照,那任意一个更新语句占的内存就太大了。

InnoDB中,每个事务都会有个事务id,叫作transaction id;它是在事务开始的时候向 InnoDB的事务系统申请的,是按申请顺序严格递增的。

而每行数据也都是有多个版本的。每次事务更新数据的时候,都会生成一个新的数据版本,并且 把transaction id赋值给这个数据版本的事务ID,记为rowtrx_id。同时,旧的数据版本要保留, 并且在新的数据版本中,能够有信息可以直接拿到它。 也就是说,数据表中的一行记录,其实可能有多个版本(row),每个版本有自己的rowtrx_id。

在这里插入图片描述

v1,v2,v3,v4都是一行数据的不同版本,v4是最新的版本,也就是当前版本;并且可以看到每个版本中都是保存了事务id的;其中的U1,U2,U3就是undo log,也就是之前提到过的两阶段提交中将操作内容写入到日志中,然后才写入磁盘;所以当需要v3版本的数据的时候,就是通过v4版本值再经过u3中记录的操作,反推到v3版本的值。

按照可重复读的定义,一个事务启动的时候,能够看到所有已经提交的事务结果。但是之后,这 个事务执行期间,其他事务的更新对它不可见。因此,一个事务只需要在启动的时候声明说,“以我启动的时刻为准,如果一个数据版本是在我 启动之前生成的,就认;如果是我启动以后才生成的,我就不认,我必须要找到它的上一个版 本”。当然,如果“上一个版本”也不可见,那就得继续往前找。还有,如果是这个事务自己更新的数 据,它自己还是要认的。

在实现上,InnoDB为了每个事务都创建一个数组,数组里存放的就是当前时刻活跃的事务,活跃的事务就是值已经创建的事务,但是还未提交的事务。数组中最小的事务id的值被称为低水位,已经创建过的事务的id的最大值+1被记为高水位;因为数组存放的是当前时刻活跃的事务,并且事务的id还是严格递增的,所以可以直到数组的最大值肯定就是此刻创建的事务id的值,数组中最大的值肯定是小于高水位的。整个数组是每个事务独有的,最小值可能相同,但是最大值肯定是不同的,因为最大值是自身的值。

在这里插入图片描述

这样,对于当前事务的启动瞬间来说,一个数据版本的rowtrx_id,有以下几种可能:

  • 如果落在绿色部分,表示这个版本是已提交的事务或者是当前事务自己生成的,这个数据是 可见的;
  • 如果落在红色部分,表示这个版本是由将来启动的事务生成的,是肯定不可见的;
  • 如果落在黄色部分, rowtrx_id在数组中,表示这个rowtrx_id对对应的事务还是属于活跃的事务,还未提交的事务的版本,所以不可见;
  • 如果落在黄色部分, rowtrx_id不在数组中,表示 rowtrx_id对应的那个事务已经不是活跃的事务,是已经提交的事务,所以数据可见。

在这里插入图片描述

做如下假设:

  1. 事务A开始前,系统里面只有一个活跃事务ID是99;
    1. 事务A、B、C的版本号分别是100、101、102,且当前系统里只有这四个事务;
    1. 三个事务开始前,(1,1)这一行数据的rowtrx_id是90。 这样,事务A的视图数组就是[99,100], 事务B的视图数组是[99,100,101], 事务C的视图数组是 [99,100,101,102]

在事务执行过程中,虽然还未提交这个事务,但是在事务中对这一行的版本更新是可以被其他事务看见的。
在这里插入图片描述

A:

  1. 查询当前版本发现版本的rowtrx_id是101,是大于事务A的活跃数组的最大值(即100),属于高水位以上,是未来事务,不可见。
  2. 经过日志存储的操作记录,计算出历史版本1的数据,看见rowtrx_id是102,还是高水位以上,不可见。
  3. 回退计算,得到历史版本2,发现rowtrx_id是90,是低水位以下,可见,所以A查询的值是1

在B事务中,有一个更新操作,如果事务B的更新操作是在事务B创建的视图中进行的,那就把事务c的操作给覆盖了。

**

所以,这里就用到了这样一条规则:更新数据都是先读后写的,而这个读,只能读当前的 更 值,称为 值 “当前读 当 ”(current read
)。

**

事务B中更新之前,先去读当前数据,所以更新的目标值已经是2了,所以给更新成3,此时数据版本更新的当前版本对应的rowtrx_id是101。

在事务B的查询中,发现当前版本的rowtrx_id是101,在事务B的活跃数组中,所以可见,看见的值是3。

如果事务C做一些改动:

在这里插入图片描述

改动就是事务C的提交是在事务B的更新操作,查询操作之后,在事务B的提交之前。但是由于之前说到的两阶段锁,新事务C的写锁还未提交,所以事务B的读锁(更新操作必须当前读)是互斥的,所以事务B只能等待锁的释放。

### MySQL视图事务的概念及用法 #### 视图 (View) 视图是一种虚拟表,其内容由查询定义。同真实的表一样,视图包含一系列带有名称的列和行数据。然而,视图并不在数据库中以存储的数据值集形式存在。行和列数据来自由定义视图的查询所引用的表,并且是在引用视图时动态生成。 创建视图可以简化复杂的 SQL 操作,隐藏底层表结构的变化,以及控制访问权限。以下是创建视图的一个例子: ```sql CREATE VIEW high_value_products AS SELECT name, price FROM products WHERE price > 100; ``` 使用视图可以通过简单的 `SELECT` 查询来获取复杂查询的结果: ```sql SELECT * FROM high_value_products; ``` #### 事务 (Transaction) 事务是一系列作为一个单元一起执行的操作集合,在这些操作全部成功完成之后才会永久保存更改;如果任何一个部分失败,则整个过程都将回滚到最初状态,以此保持数据一致性[^2]。 开始一个新的事务并应用隔离级别的语句如下所示: ```sql SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ; START TRANSACTION; -- 执行一些SQL命令... COMMIT; -- 或者 ROLLBACK 如果出现问题的话. ``` #### 隔离级别与锁机制 MySQL 提供了四个标准的事务隔离级别,其中每一个都提供了不同程度上的保护以防其他并发事务的影响。较低级别的隔离允许更高的并发度但可能导致脏、不可重复等问题的发生;而较高的隔离级则反之[^3]。 - **READ UNCOMMITTED**: 可能会看到未提交的新记录或更新。 - **READ COMMITTED**: 不可见未提交变更,防止脏- **REPEATABLE READ**(默认): 同一事务内多次取相同结果集,避免幻影现象。 - **SERIALIZABLE**: 完全串行化的处理方式,提供最严格的隔离程度。 关于锁机制方面,在较高隔离等级如 SERIALIZABLE 下,为了确保数据一致性和避免冲突,可能会施加更广泛的锁定策略,比如间隙锁(gap locks) 和临键锁(next-key locks),即使在某些版本中可能表现不同[^4]。 #### 示例代码展示 这里给出一段综合性的示例代码,展示了如何在一个高隔离级别的环境中安全地进行多条记录的同时修改: ```sql SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE; START TRANSACTION; UPDATE accounts SET balance = balance - 100 WHERE user_id = 'alice'; UPDATE accounts SET balance = balance + 100 WHERE user_id = 'bob'; COMMIT; ``` 此段脚本首先设置了当前会话的最大隔离水平——即串行化(SERIALIZABLE),接着启动了一个新的事务,并在这个上下文中进行了两个账户之间的转账操作最后通过提交确认所有改变生效。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值