假设当前数据库里有下面这张表。

user表数据库原始状态
老规矩,以下内容还是默认发生在innodb引擎的可重复读隔离级别下。

都是select结果却不同
大家可以看到,线程1,同样都是读 age >= 3 的数据。第一次读到1条数据,这个是原始状态。这之后线程2将id=2的age字段也改成了3。
线程1此时再读两次,一次读到的结果还是原来的1条,另一次读的结果却是2条,区别在于加没加for update。
为什么同样条件下,都是读,读出来的数据却不一样呢?
可重复读不是要求每次读出来的内容要一样吗?
要回答这个问题。
我需要从盘古是怎么开天辟地这个话题开始聊起。
不好意思。
失态了。
那就从事务是怎么回滚的开始聊起吧。
事务的回滚是怎么实现的
我们在执行事务的时候,一般都是下面这样的格式
begin;
操作1;
操作2;
操作3;
xxxxx
....
commit;
在提交事务之前,会执行各种操作,里面可以包含各种逻辑。
只要是执行逻辑,那就有可能会报错。
回想下事务的ACID里有个A,原子性,整个事务就是个整体,要么一起成功,要么一起失败。

ACID
如果失败了的话,那就要让执行到一半的事务有能力回到没执行事务前的状态,这就是回滚。
执行事务的代码就类似写成下面这样。
begin;
try:
操作1;
操作2;
操作3;
xxxxx
....
commit;
except Exception:
rollback;
如果执行rollback能回到事务执行前的状态的话,那说明mysql需要知道某些行,执行事务前的数据长什么样子。
那数据库是怎么做到的呢?
这就要提到undo日志了,它记录了某一行数据,在执行事务前是怎么样的。
比如id=1那行数据,name字段从**“小白"更新成了"小白debug”**,那就会新增一个undo日志,用于记录之前的数据。

undo日志会记录之前的数据
由于同时并发执行的事务可以有很多,于是可能会有很多undo日志,日志里加入事务的id(trx_id)字段,用于标明这是哪个事务下产生的undo日志。
同时将它们用链表的形式组织起来,在undo日志里加入一个指针(roll_pointer),指向上一个undo日志,于是就形成了一条版本链。

undo日志版本链
有了这个版本链,当某个事务执行到一半发现失败时,就直接回滚,这时候就可以顺着这个版本链,回到执行事务前的状态。
当前读和快照读是什么
有了上面的undo日志版本链之后,我们可以看到最新的数据在表头,在这之后的都是一个个旧的数据版本。不管是最新的,还是旧的数据版本,我们都叫它数据快照。
当前读,读的就是版本链的表头,也就是最新的数据。

最低0.47元/天 解锁文章
4138

被折叠的 条评论
为什么被折叠?



