事务
如果是可重复读隔离级别的事务,事务 T 启动的时候会创建一个视图 read-view,之后事务 T 执行期间,即使有其他事务修改了数据,事务 T 看到的仍然跟在启动时看到的一样。
但是如果一个事务要更新一行,如果刚好有另外一个事务拥有这一行的行锁,它会被锁住,进入等待状态。问题是,既然进入了等待状态,那么等到这个事务自己获取到行锁要更新数据的时候,它读到的值又是什么呢?
事务启动的时间
时刻1——start transaction with consistend sanpshot(事务A)
时刻2——start transaction with consistend sanpshot(事务B)
时刻3————update t set k=k+1 where id =1(事务C)
时刻4——update t set k=k+1 where id =1;select k from t where id =1;(事务4)
时刻5——elect k from t where id =1;commit(事务A)
时刻6——commit(事务B)
事务 C 没有使用更新语句是因为事务 A 本身就是一个事务,语句更新后自动提交
start 不是事务启动的时间。在执行到他们之后的第一个操作 InnoDB ,事务才真正的启动,如果想马上启动一个事务,那么旧可以使用start transaction with consistent snapshot
在 MySQL 里,有两个“视图”的概念:
一个是 view。它是一个用查询语句定义的虚拟表,在调用的时候执行查询语句并生成结果。创建视图的语法是 create view … ,而它的查询方法与表一样。
另一个是 InnoDB 在实现 MVCC 时用到的一致性读视图,即 consistent read view,用于支持 RC(Read Committed,读提交)和 RR(Repeatable Read,可重复读)隔离级别的实现。
“快照”在 MVCC 里是怎么工作的?
MVCC 是对整个库的快照,并是整个库的拷贝
每个事务都需要向 InnoDB 的事务系统申请一个唯一递增的事务 ID
每行数据是有多个版本的,每个版本都有自己的 row trx_id
row trx_id 是就是每个事务从事务系统申请一个唯一递增的事务 ID
旧的数据版本要保留,并且在新的数据版本里面,有信息可以直接拿到它。
在可重复读隔离级别下,事务在启动的时候就“拍了个快照”。注意,这个快照是基于整库的。
这时,会说这看上去不太现实啊。如果一个库有 100G,那么我启动一个事务,MySQL 就要拷贝 100G 的数据出来,这个过程得多慢啊。可是,我平时的事务执行起来很快啊。
实际上,我们并不需要拷贝出这 100G 的数据。我们先来看看这个快照是怎么实现的。
InnoDB 里面每个事务有一个唯一的事务 ID,叫作 transaction id。它是在事务开始的时候向 InnoDB 的事务系统申请的,是按申请顺序严格递增的。
按照可重复读的定义,一个事务启动的时候,能够看到所有已经提交的事务结果。但是之后,这个事务执行期间,其他事务的更新对它不可见。
因此,一个事务只需要在启动的时候声明说,“以我启动的时刻为准,如果一个数据版本是在我启动之前生成的,就认;如果是我启动以后才生成的,我就不认,我必须要找到它的上一个版本”。
在实现上, InnoDB 为每个事务构造了一个数组,用来保存这个事务启动瞬间,当前正在“活跃”的所有事务 ID。“活跃”指的就是,启动了但还没提交。
每个事务都有一个数组,这个视图数组把所有的 row trx_id 分成几种不同的情况:已提交事务,未提交事务,未开始事务 已提交事务是可见的。 未开始事务是不可见的。 未提交事务有两种情况:如果自己的 row tri_id 在数组里面,那么证明这个版本是由已提交的事务生成的,可见。如果是不再数组,那么是未提交的事务生成的。不可见。
InnoDB 利用所有的数据都有多个版本,实现了秒级创建快照的能力。
一个数据版本,对于一个事务视图来说,除了自己的更新总是可见的以外。有三种情况:版本未提交,不可见。版本已经提交,但是是在视图之后创建提交的,不可见;版本已提交,但是是在视图创建之前提交的,可见。
在可重复读隔离级别下,在事务开始的时候创建一致性视图,之后事务的每个查询共用这一个视图,在读提交隔离级别下,每个语句执行前都会重新算出一个新的视图。
总结:
InnoDB 的行数据有多个版本,每个行数据有自己的 row trx_id.每个事务或者语句有自己的一致性视图。普通的查询语句是一致性读。一致性读会根据 row trx_id 和一致性视图确定数据版本的可见行。
可重复读:查询只承认在事务启动之前就提交的数据 读提交:查询只承认在语句启动之前已经提交的数据 当前读:总是读出已经提交完成的最新版本
“可重复读”隔离级别下的视图是事务启动的时候创建的。整个事务期间都用这个视图。 “读提交”隔离级别下的视图是是在每个 SQL 语句开始执行的时候创建的。 “读未提交“的隔离级别下直接返回记录上的最新值,没有视图概念。 “串行化”的隔离级别下直接用加锁的方式避免串行访问。
- 读未提交:造成脏读问题。
- 读提交:造成不可重复读问题
- 可重复读:解决了不可重复读问题,但是造成幻读问题
什么时候需要“可重复读”的场景呢? 数据校对的场景:判断上个月的余额和当前余额的差额,是否与本月的账单明细一致。一定希望在校对过程中,即使有用户发生了一笔新的交易,也不影响的校对结果。