一叶而不知秋
向管中窥豹寻知外,坐井观天又出来。
背景条件
autocommit=1;隔离级别为repeatable read。
上表中 trx_1,trx_2,trx_3 代表三个客户端启动的三个事务,语句从上到下依次执行,上面三个查询语句的结果分别是多少。
先说下答案:(1)=>3;(2)=>1;(3)=>2。接下来我们分析一下为什么会得到上面的结果。
MVCC
MySQL隔离机制通过MVCC实现。简单来说就是:MySQL事务启动时会生成一个机遇整库的“快照”(read-view),事务中的读都是“快照”读。
快照并非真实存在,而是根据undo log计算出的
数据库中的每行记录后面有三个隐藏列,其中一个记录事务当前id的trx_id(并不是启动快照的事务id,而是最新更新该行并且提交的事务id)。如下图:
对表中某一列进行了三次更新,得到四个read-view,中间的U1,U2,U3代表undo log;第一个事务,id为15,提交后将k的值改为10,行上隐藏的trx_id更改为15。后面类似。
根据可重复读的定义,可以得到:当前事务只承认在自己事务启动前已经提价的事务修改的数据,也就是说trx_id小于当前事务id,并且在该事务启动前已经提交。
查询
结合本例来看,假设trx_1事务开启前系统只有一个活跃事务Id:99;该行的trx_id = 90;trx_1,trx_2,trx_3启动后的事务id分别为100,101,102。
经过两次更新后,该行的状态如下图:
trx_1开始读的时候,从当前版本开始读也就是trx_2更新后的版本开始读。但是trx_2事务未提交并且trx_id=101 > 100,因此对于trx_1不可见;根据undo log找上一个版本也就是trx_3更新的版本,事务已经提交,但是trx_id=102 > 100,对于trx_1不可见;继续找上一个版本,也就是trx_id = 90的版本,已经提交,且trx_id小于100,于是trx_1返回这个版本的值,也就是1。
更新
更新的流程是先读后写,但是考虑上面的分析,trx_2的update后select的值好像不正确。根据可重复读的定义,应该是2,但是返回了3。
MySQL中,更新语句的读是当前读。也就是update语句读到的是最新一次更新的值,不管更新该行的事务有没有提交。除了update语句,加锁读也是当前读,即select语句后加上lock in share mode或者for update,读到的都是当前值,而不是“快照”中的值。
总结
可重复读的核心是一致性读。
更新数据,或者加锁读是当前读。
当记录的行锁被其他事务占用的话,需要进入锁等待。
可重复读隔离级别下,当前事务启动前提交的事务可见,其余不可见。
读提交隔离级别下,当前查询语句前提交的事务可见,其余不可见。
你如果想学技术 | 屯干货 | 聊职场
领取专属 10元无门槛券
私享最新 技术干货