前言
mvcc
MVCC(Mutil-Version Concurrency Control),就是多版本并发控制。简单来说,每条数据其实都有两个隐藏字段,一个是trx_id,一个是roll_pointer,trx_id就是最近一次更新这条数据的事务id,roll_pointer就是指向更新这个事务之前生成的undo log(这里不展开叙述)
下面举个栗子:
-
事务A在之前插入了一行数据,值是A,事务已提交。会出现下图所示的结构 -
事务B再次修改这行数据,变更为值B,会出现下图所示的结构,B的这行的roll_pointer会指向刚刚的事务A的undo_log -
事务C再次修改这行数据,变更为值C,C的这行的roll_pointer会指向刚刚的事务B的undo_log
多个事务串行执行的时候,每个人修改了一行数据,都会更新隐藏字段txr_id和roll_pointer,同时之前多个数据快照对应的undo log,会通过roll_pinter指针串联起来,形成一个重要的版本链!
readview
执行一个事务的时候,会生成一个ReadView,里面比较关键的东西有4个 一个是m_ids,MySQL里执行还没提交的,换句话说就是活跃的事务 一个是min_trx_id,就是m_ids里最小的值; 一个是max_trx_id,下一个要生成的事务id,就是最大事务id; 一个是creator_trx_id,当前这个事务的id
Read Committed隔离级别
定义
简称为 RC,一句话总结。 事务运行期间,只要别的事务修改数据提交了,就可以读到别的事务修改的数据。当你事务处于RC隔离级别的时候,他是每次发起查询,都重新生成一个ReadView!
解读
举个栗子:
目前有一个事务A已提交,值为A。 然后现在活跃着两个事务,一个是事务B(id=20),一个是事务C(id=30),此时事务B将这行数据更新为B,如上图所示。然后事务C发起查询,此时会生成一个readview,min_trx_id=20,max_trx_id=31,creator_trx_id=30,m_ids in (20,30)。发现当前这条数据的trx_id是20。也就是说,属于ReadView的事务id范围之间[20,31],说明是他生成ReadView之前就有这个活跃事务,是这个事务修改了这条数据的值,但是此时这个事务B还没提交,所以ReadView的m_ids活跃事务列表里,是有[20, 30]两个id的,所以此时根据ReadView的机制,此时事务C是无法查到事务B修改的值B的。
然后会顺着undo_log版本链往下找,找到trx_id=10这条记录,小于[20,31]数组的,证明是开启readview前的已提交事务,因此可以查到这个值A
下一步,事务B提交了,事务C再次发起查询,生成的readview,事务B不在活跃数组里了,min_trx_id=20,max_trx_id=31,creator_trx_id=30,m_ids in (30),活跃事务列表里会有一个30,因此顺着undo_log版本链往下查询,值B这条数据就被查出来。
Read Repeatable 隔离级别
定义
RR级别下,一个事务读一条数据,无论读多少次,都是同一个值,同一个readview,就算别的事务修改数据后重新读,还是原来的值。
解读
我们沿用刚刚的案例的这一步
下一步,事务B提交了,事务C再次发起查询
此时生成的readview是什么呢?因为是RR级别,生成readview是跟第一次查询是一样的, min_trx_id=20,max_trx_id=31,creator_trx_id=30,m_ids in (20,30)。活跃事务列表里存在(20,30),因此这两条数据都不会被查出来,顺着undo_log版本链往下查询,最终值A这条数据被查出。
如何解决幻读
假设现在事务A已提交,事务BC正活跃,事务C先用select count(*) from x where id > 0 来查询,此时可能查到的就是一条数据,读到的是这条数据是trx_id = 10的版本。然后事务B提交了,事务C再次执行查询,因为第一次查与第二次查的readview是一样的,trx_id = 20 还是在活跃事务中,此行数据不可见。所以事务C还是只能查到 trx_id = 10 这行数据
|