1. 行锁和事务
- 事务:可重复读级别下,启动时会创建一个视图,保证操作时数据一致.
- 锁:事务在执行时,如果指定行有锁,事务会进行等待.
1.1. 实例
CREATE TABLE `t` (
`id` int(11) NOT NULL,
`k` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB;
insert into t(id, k) values(1,1),(2,2);
过程:
- 事务C:没有显示的使用
begin/commit
,一个update语句本身就是一个事务.执行完成自动提交 - 事务B:立刻创建视图,更新后查询
- 事务A:只读事务,直接查询
结果:
- 事务B:3
- 事务A:1
1.1.1. 事务中视图生成时机
默认autocommit=1
begin/start transaction
不是事务的起点- 他们执行后第一个执行的SQL语句才是
- 即在第一个SQL执行时,生成视图
start transaction with consistent snapshot
:立刻启动事务- 立刻生成视图
1.2. MySQL的两个视图的概念
- view:查询语句定义的虚拟表,在调用时执行查询语句并生成结果:
create view
- InnoDB实现MVCC时用的一致性视图(consistent read view):
- 用于支持读提交和可重复读隔离级别的实现.
- 其没有物理结构,定义事务执行期间,事务能看到什么数据.
2. 快照的工作原理
可重复读级别下:事务启动时会对整个库拍快照
2.1. 快照的实现
- InnoDB中每个事务有一个唯一的事务ID:
transaction id
.- 事务id在事务开始时向系统申请.
- 具体id按照申请顺序递增
- 每行数据也是有多个版本.每次事务更新数据时,会生成一个新的数据版本.并且把
transaction id
复制给该数据版本的事务ID:row trx_id
- 同时,保留就的数据版本
- 并且新的数据版本中可以拿到旧的数据.
- 数据表中的每一行都可能有多个版本
row
,每个版本有自己的row trx_id
- 上面的虚线是
undo log
- v1,v2,v3不是真实存在的,而是根据当前版本和undo_log计算出来的
2.2. 快照具体分析
可重复读级别下:
- 启动事务A
- 事务A能够看到所有已经提交的事务的结果
- 在事务A执行期间,无法看到其他事务的结果
- 事务A启动时声明:
- 以其
transaction id
为准,数据在其id前面的,就可以用 - 以其
transaction id
为准,数据在其id后面的,数据就对事务A不可见,需要当前数据之前的版本 - 如果是事务A自己修改的数据,是可以用的.
- 以其
- 事务A启动时,会有一个数组:记录事务A启动瞬间,当前活跃的事务id(启动了,尚未提交的事务)
- 数组中最小的ID称为低水位
- 数组中最大的ID+1称为高水位
- 数组+高水位:形成了当前事务的一致性视图
- 数据版本的可见性:根据
row trx_id
和上面的一致性视图对比得到
2.2.1. 可见性规则
一个数据版本的row trx_id
的可能性:
- 绿色部分:该版本是以提交的事务或者当前事务自己生成的
- 可见
- 红色部分:该版本是将来启动的事务生成的
- 不可见
- 黄色部分:
row trx_id
在数组中,该版本是由还未提交的事务生成- 不可见
row trx_id
不在数组中,该版本是由已经提交的事务生成的.- 可见
3. 具体例子
3.1. 前置条件
- 事务A开始前,系统里只有一个活动的事务ID是99
- 事务A,B,C的ID分别是100,101,102.且当前系统只有这4个事务
- 3个事务开始前,(1,1)这行数据的
row trx_id
是90.
3.2. 每个事务的事务数组
- 事务A:[99,100]
- 事务B:[99,100,101]
- 事务C:[99,100,101,102]
3.3. 执行顺序
- 事务C首先执行
- 个人猜测:应该是事务C直接就是update (直接生成了view并执行了,另外的两个,首先生成view)
- 数据更改:
- 90版本—–>102版本===(1,1)变成(1,2)
- 事务B执行
- 数据更改:
- 102版本—–>101版本===(1,2)变成(1,3)
- 数据更改:
- 事务A执行
- 数据查找:事务A当前的数组是[99,100]
- 读当前版本,发现是(1,3)—–>是101版本,是高水位,红色区,不可见
- 计算上一个版本,发现是(1,2)—–>是102版本,是高水位,红色区,不可见
- 计算上一个版本,发现是(1,1)—–>是90版本,是低水位,绿色区,可见.
- 查询结果:(1,1)
- 数据查找:事务A当前的数组是[99,100]
3.4. 原则
对于一个事务视图来说,除了自己的更新总是可见以外
- 版本未提交,不可见
- 版本已提交,但是是在视图创建后提交的,不可见
- 版本已提交,而且是在视图创建前提交的.可见
3.5. 事务B更新时为什么能看到事务C的提交
原因:
事务B更新前如果查询数据,还是1(因为事务B生成了视图,事务C才提交的.对事务B不可见)
但是更新的时候,不能在历史数据上更新(否则事务C的提交就丢失了),必须在当前数据上更新.
更新数据是
先读后写
.
- 读是读当前值,称为当前读.当前值是(1,2)===>更新后是(1,3),
row trx_id
版本号是101- 事务B再次查询时,自己的版本号==该数据的版本号====>数据可以直接使用
3.5.1. 当前读的其他应用
除了update以外,如果select语句要加锁,也是当前读
-- 如果事务A使用如下方法查询,也是直接获得3,即101版本的数据
select k from t where id=1 lock in share mode;
select k from t where id=1 for update;
4. 事务C使用显示提交
4.1. 执行顺序
两阶段锁协议:
- 事务C更新后没有立刻提交
- 数据更新:
- 生成了(1,2)这个数据版本===>数据版本102
- 该行的写锁还没有释放
- 数据更新:
- 事务B也更新了,也没有提交
- 数据未更新:
- 事务B要更新,必须是是当前读,且必须加锁.
- 因为事务C的锁还没有释放,事务B等待.hi到事务C’释放锁,才能继续当前读
- 数据未更新:
5. 概念和原理
5.1. 可重复读实现
可重复读的核心:一致性读(consistent read)
- 事务更新数据时,只能使用当前读(读取当前的值)
- 如果当前记录的行锁被其他事务杭甬,进入锁等待
5.2. 读提交级别和可重复读级别的区别
- 可重复读隔离级别下:事务开始时(执行第一条语句,或者使用
start transaction with consistent snapshot
立即创建)创建一致性视图,hi后事务中的其他**查询(select)**公用这个一致性视图 - 读提交:没一个语句执行前都会重新计算一个新的视图
6. 读提交级别下上面的例子
读提交中start transaction with consistent snapshot
语句没用==start transaction
6.1. 执行顺序
- 事务C执行
- 数据更改
- 90版本===>102版本,数据变成(1,2)===>数据提交
- 数据更改
- 事务B执行
- 数据更改
- 102版本===>101版本,数据变成(1,3)===>数据未提交
- 数据更改
- 事务A执行查询.
- 数据查询
- 101版本还没有提交,不可见
- 102版本提交了,可见
- 数据查询
结果:
- 事务A:2
- 事务B:3
7. 总结
- Innodb中,每个版本有自己的
row trx_id
- 每个事务有自己的一致性视图
- 普通查询语句是一致性读
- 一致性读会根据
row trx_id
和一致性视图来确定数据版本的可见性.
- 一致性读会根据
- 可重复读:只承认事务启动前提交完成的数据
- 读提交:只承认语句启动前提交完成的数据
- 当前读:读取已经提交完成的最新版本
- 为什么表结构不支持可重复读?
- 因为表结构没有对应的行数据===>没有对应的
row trx_id
===>只能当前读 - MySQL8.0可以把表结构放到InnoDB字典中,以后也有可能支持表结构的可重复读
- 因为表结构没有对应的行数据===>没有对应的
7.1. 问题
CREATE TABLE `t` (
`id` int(11) NOT NULL,
`c` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB;
insert into t(id, c) values(1,1),(2,2),(3,3),(4,4);
-- 一个事务
begin;
select * from t;
-- 数据正确
update t set c=0 where id=c;
select * from t;
-- 数据不变;
-- 该语句不起效果,有哪些可能?
可能1:
- 事务A执行第一个语句创建一致性视图
- 事务B执行.更新数据并提交
- 事务A的update语句当前读
- 读的结果(1,2),(2,3),(3,4),(4,5)
- 执行更新,条件不匹配
- 事务A最后的select(使用创建的一致性视图)
可能2:
- 事务B’执行,创建一致性视图
- 事务A执行,创建一致性视图
- 事务B更新,并提交
- 事务A的update当前读
- 读的结果(1,2),(2,3),(3,4),(4,5)
- 执行更新,条件不匹配
- 事务A最后的select(使用创建的一致性视图)