当前读和快照读
InnoDB 给每一个事务生成一个唯一事务 ID 的方法称为生成快照,因此这种场景称为快照读。
但是对于更新数据不能使用快照读,因为更新数据时如果使用快照读会可能会覆盖其他事务的更改。
另外查询时如果加锁也会采用当前读的方式。当前读就是读这个数据最新的提交数据。
InnoDB 的多版本并发控制实现了在串行化的隔离级别下读不加锁,提高了并发性能。
当前读
当前读的场景有下面几种:
update ... (更新操作)
delete ... (删除操作)
insert ... (插入操作)
select ... lock in share mode (共享读锁)
select ... for update (写锁)
当前读,读取的是最新版本,并且对读取的记录加锁,阻塞其他事务同时修改相同记录,避免出现安全问题。
例如,假设要update一条记录,但是另一个事务已经delete这条数据并且commit了,如果不加锁就会产生冲突。所以update的时候肯定要是当前读,得到最新的信息并且锁定相应的记录。
快照读
快照读的场景:
单纯的select操作
(不包括上面当前读的select ... lock in share mode,select ... for update)
Read Committed隔离级别:每次select都生成一个快照读
Read Repeatable隔离级别:开启事务后第一个select语句才是快照读的地方,而不是一开启事务就快照读
例子
下面通过一个例子来理解快照读和当前读:
首先建一个表 t,并插入一条数据。
mysql-> create table t(k int)ENGINE=InnoDB;
mysql-> insert into t(k) values (1);
然后将事务的隔离级别设置为 REPEATABLE-READ,接着开启三个事务,并按照下面的顺序进行执行。
MySQL 中事务开始的时间
一般我们会认为 begin/start transaction 是事务开始的时间点,也就是一旦我们执行了 start transaction,就认为事务已经开始了,其实这是错误的。事务开始的真正的时间点(LSN),是 start transaction 之后执行的第一条语句,不管是什么语句,不管成功与否。
但是如果你想要达到将 start transaction 作为事务开始的时间点,那么我们必须使用:
start transaction with consistent snapshot
它的含义是:执行 start transaction 同时建立本事务一致性读的 snapshot . 而不是等到执行第一条语句时,才开始事务,并且建立一致性读的 snapshot .
效果等价于: start transaction 之后,马上执行一条 select 语句(此时会建立一致性读的snapshot)。
事务A | 事务B | 事务C | 说明 |
---|---|---|---|
start transaction with consistent snapshot | 开启事务A | ||
start transaction with consistent snapshot | 开启事务B | ||
select k from t; | 事务A读取结果是1(快照读) | ||
select k from t; | 事务B读取结果是1(快照读) | ||
update t set k = k + 1; | 事务C提交的k为2 | ||
update t set k = k + 1; | update 语句进行了一次当前读将 k 的值更新为事务 C 已经提交的结果 2,并且在此基础上再加1得到3 | ||
select k from t; | 执行了 update 操作时会创建一个新版本的数据,并且将自己的事务 ID 作为该数据的版本号,因此在该事务内可以读到自己更新的数据。因此事务 B 最后一次查询的结果是 3。 | ||
commit; | 事务B提交 | ||
select k from t; | 事务A读取结果是1(快照读) | ||
commit; | 事务A提交 | ||
select k from t; | 读取结果是 3 |
图示:
参考
https://juejin.im/post/6844903928627200007
https://www.cnblogs.com/digdeep/p/4947694.html