mysql mvcc 并发update_MySQL原理——多版本并发控制(MVCC)案例解读

2ff34e647e2e3cdfd8dca593e17d9b0a.png多版本并发控制(MVCC)是 MySQL 的 InnoDB 存储引擎实现隔离级别的一种具体方式,用于实现读已提交和可重复读这两种隔离级别。对于读未提交隔离级别总是读取最新的数据行,无需使用 MVCC。可串行化隔离级别需要对所有读取的行都加锁,单纯使用 MVCC 无法实现。

事务的两种启动方式start transaction(在执行到它们之后的第一个操作 InnoDB 表的语句时,事务才真正启动)

start transaction with consistent snapshot; (该命令执行后事务立刻启动)

MySQL中视图的两种概念view:使用创建视图语句,通过查询语句得到的虚拟表,逻辑上独立,不受真实表结构变化带来的影响。

consistent read view:一致性视图,用于支持读提交、可重复读隔离级别的实现。

InnoDB中的事务和数据版本每个事务都有一个唯一的事务ID,即 transaction_id,按照向系统申请时间严格递增。

每行数据都有多个版本,每次事务更新数据,都会生成一个新的数据版本,并将更新操作的事务ID赋值给当前数据版本的事务ID,即:row_trx_id = transaction_id

mysql-row-trx-id.png

说明:v1、v2、v3并不是正式存在的,但可以通过U1、U2、U3和当前数据版本V4计算出来。

U1、U2、U3就是undo log (回滚日志) 重点理解:

在 可重复读的隔离级别 下,一个事务开启时,InnoDB会为之创建一个数组存放当前正在活跃的所有事务ID,活跃的事务是指启动还未提交的事务,该数组就是一致性视图。事务开启后,当要查询数据的时候,可能数据版本又被其他事务更新了好几个版本,当前数据版本不再是之前开启事务的数据版本。但当前事务只能看到他开启事务时的数据版本,之后的数据版本是不可见的,所以需要回推计算到之前开启事务时的数据版本。当然当前事务自己做的更新对自己来说肯定是可见的。

MVCC举例

假设事务A B C前,系统中有一个活跃事务ID 99,A、B、C的事务ID分别为:100、101、102

假设事务A开启时,数据版本是90(1,1)

事务C先对数据做更改,数据版本变为102(1,2)

事务B再对数据做变更,数据版本变为101(1,3)

事务C没有显示 start 开启事务,表示自动开启事务,自动提交。事务A事务B事务Cstart transaction with consistent snapshot;

start transaction with consistent snapshot;

update t set k=k+1 where id=1;

update t set k=k+1 where id=1;

select t from t where id=1;

select k from t where id =1;

commit;

commit;

则后面事务的一致性视图如下:

A[99,100]、B[99,100,101]、C[99,100,101,102]

mysql-consistent-read.png 一个数据版本,对于一个事务的一致性视图,可见性分析规则:

1. 数据版本未提交,不可见;

2. 数据版本已提交,但是在事务视图创建后提交的,不可见;

3. 数据版本已提交,而且在事务视图创建前提交的,可见;

案例数据版本的可见性分析结果:

- 数据版本101(1,3) 没提交,对事务A不可见。

- 数据版本102(1,2)已经提交,但在事务A创建事务视图后,所以对A不可见。

- 数据版本90(1,1)在事务A视图数组创建之前就提交了,对A可见。

所以事务A读取到的值为1。分析下来,虽然数据被修改过,但事务A读取的仍然是1,称之为一致性读。

Update操作

你可能会问,数据版本102(1,2)虽然已经提交,但在事务B视图创建之后,为什么事务B更新操作会形成(1,3),这跟一致性读冲突了呀?update的规则:更新数据都是先读后写,读是当前读,基于当前最新的数据版本。

根据一致性视图,如果事务B更新前做一次查询,查询的结果肯定是数据版本90(1,1)。但update时,就不能用历史版本的数据版本了,否则事务C做的更新就丢失了,所以事务B的更新操作是基于数据版本102(1,2),更新之后就是数据版本101(1,3)

除了update操作,select如果加上锁,也会是当前读。1

2select * from t where id = 1 lock in share mode(读锁S,共享锁)

select * from t where id = 1 for update(写锁X,排它锁)

在 读已提交隔离级别 下的区别

区别在于视图的创建时机:可重复读是在开启事务的时候创建一致性视图,之后基于一致性视图进行一致性读,写是当前读。

读已提交的隔离级别,是在执行语句时,生成一个新的视图。

基于上述实例和可见性分析规则,在读已提交的隔离级别下结果为:(1,3)未提交,不可见

(1,2)已提交,可见

所以A查询是:2,B查询是:3 总结:

InnoDB行数据有多个版本,每个数据版本有自己的row_trx_id,每个事务或者语句有自己的一致性视图。普通查询语句是一致性读,一致性读会根据row_trx_id和一致性视图确定数据版本的可见性:对于可重复读,查询只承认在事务开启前已提交完成的数据

对于读已提交,查询只承认语句启动前已经提交完成的数据

而当前读,总是读取已经提交完成的最新版本。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值