MySql事务实现的机制:MVCC

MySql事务实现的机制:MVCC

这一篇将简单说明一下最近学习了Mysql的事务实现的简单理解。

如果存在一张A表,id=1,name=2,此时,存在多个事务对该表进行处理,那么会怎样呢?

假设我们会存在多个事务同时进行,

0 事务2 事务3 事务4 查询1 查询2
1 begin begin begin begin Begin
2 Update A set name='name2' where id=1
3 Update A set name='name3' where id=1
4 Update A set name='name4' where id=1
5 Commit
6 Select name from A where id = 1
7
8
9

首先运行第2行sql

当程序运行到第2行的时候,由于事务1对A表进行了更新操作,此时会产生一个事务id,我们假设该id的值为2。其中事务id是一个自增长的id,仅当事务中进行了更新操作的时候生成,同时该事务id整个数据库共用。

此时,mysql在一个叫做undo的回滚日志栈中存在着一条旧有的数据,在更新操作执行的时候,会押入当前更新的数据,同时记录下当前的事务id,并且产生一条指向前面押入的旧有数据的回滚指针。

即此时,回滚日志栈的结构如下


image-20200229213312983.png

同理,我们接下来运行第3行sql

0 事务2 事务3 事务4 查询1 查询2
1 begin begin begin begin Begin
2 Update A set name='name2' where id=1
3 Update A set name='name3' where id=1

由于事务3运行了新的更新语句,一个新的事务id3生成,并且押入了undo中,即此时结构如下

image-20200229213727288.png

同理,运行第4,5行

0 事务2 事务3 事务4 查询1 查询2
1 begin begin begin begin Begin
2 Update A set name='name2' where id=1
3 Update A set name='name3' where id=1
4 Update A set name='name4' where id=1
5 Commit

之后结构如下

image-20200229213944090.png

运行第6行

0 事务2 事务3 事务4 查询1 查询2
1 begin begin begin begin Begin
2 Update A set name='name2' where id=1
3 Update A set name='name3' where id=1
4 Update A set name='name4' where id=1
5 Commit
6 Select name from A where id = 1

我们可以看到,这一行的操作是查询操作。对于该查询操作,我们如何保证该操作的正确性?

当第6行执行查询操作的时候,会生成一个叫做read-view的一致性视图,该视图的结构如下

  • [min_id,min_id+1,...]-max_id

如上图所示,该视图的结构由一个id数组和一个id组成;

其中,数组包含了所有未提交的事务的id,而其中min_id是未提交的最小事务id。max_id则是已经创建的最大事务id,该id不关心是否已经提交。

那么,结合我们的事务的执行情况,则第六行生成的read-view为[2,3]-4。

接下来我们看图

image-20200229220109912.png

根据min_id和max_id的区分,我们将每个事务id(trx_id)区分为;

  1. 已经提交的事务id;(trx_id
  2. 未提交或者可能提交了事务id;(min_id<=trx_id<=max_id)
  3. 当前查询语句之后才开始的事务id(max_id

然后,通过使用生成的read-view,从undo栈的栈顶开始逐行查询,之后通过以下规则鉴定可见性,如果当前查询行的数据可见,将返回对应的结果。

一、 查询行的版本号如果小于min_id,即为已经提交的事务(trx_id

结合上面的规则,我们来看这条查询语句:

  1. 语句执行后,read-view=[2,3]-4
  2. 查询栈顶,该行的版本号为4,即属于第三种情况,并且该事务已经提交,不在数组中,可见,返回结果:name4

运行第7,8行

0 事务2 事务3 事务4 查询1 查询2
1 begin begin begin begin Begin
2 Update A set name='name2' where id=1
3 Update A set name='name3' where id=1
4 Update A set name='name4' where id=1
5 Commit
6 Select name from A where id = 1
7 Update A set name='name2-1' where id=1
8 Update A set name='name2-2' where id=1

此时undo结构如下

image-20200229222812963.png

运行行数9

0 事务2 事务3 事务4 查询1 查询2
1 begin begin begin begin Begin
2 Update A set name='name2' where id=1
3 Update A set name='name3' where id=1
4 Update A set name='name4' where id=1
5 Commit
6 Select name from A where id = 1
7 Update A set name='name2-1' where id=1
8 Update A set name='name2-2' where id=1
9 Select name from A where id = 1

第9行我们在查询1中,运行了新的查询语句,我们来看这条查询语句

image-20200229223451610.png
  1. 语句执行后,read-view=[2,3]-4
  2. 从栈顶开始查询,该行的版本号为2,则属于第三种情况,同时存在于数组中,即该行数据不可见
  3. 继续查询下一行,结果同2
  4. 继续查询下一行,版本号为4,不存在于数组中,可见,返回结果name4

运行第10,11,12行

0 事务2 事务3 事务4 查询1 查询2
7 Update A set name='name2-1' where id=1
8 Update A set name='name2-2' where id=1
9 Select name from A where id = 1
10 commit; Update A set name='name3-1' where id=1
11 Update A set name='name3-2' where id=1
12

这行我们提交了事务2,而且事务3进行了两次更新,此时数据格式如下:

image-20200229224639347.png

运行第11行

0 事务2 事务3 事务4 查询1 查询2
7 Update A set name='name2-1' where id=1
8 Update A set name='name2-2' where id=1
9 Select name from A where id = 1
10 commit;
Select name from A where id = 1 Select name from A where id = 1

这一行我们将查询1和查询2放置在一起进行比较。

首先我们看查询2的语句:

image-20200229224719402.png
  1. 查询语句生成read-view=[3]-4
  2. 从栈顶开始查询,该行的版本号为3,则属于第三种情况,同时存在于数组中,即该行数据不可见
  3. 继续查询下一行,结果同2
  4. 继续查询下一行,版本号为2,不存在于数组中,可见,返回结果name2-1

接下来,我们看查询1的查询情况:

image-20200229225016097.png

如上图所示,mysql的事务分为可重复读,与不可重复读的情况。

如果是重复读的情况,每次查询将使用第一次生成的read-view;而如果是不可重复读的情况,那么每次查询都将使用新的read-view。即如果是可重复,处理情况如下

  1. 查询语句生成read-view=[2,3]-4
  2. 从栈顶开始查询,该行的版本号为3,则属于第三种情况,同时存在于数组中,即该行数据不可见
  3. 继续查询下一行,结果同3
  4. 继续查询下一行,版本号为2,则属于第三种情况,同时存在于数组中,即该行数据不可见
  5. 继续查询下一行,结果同4
  6. 继续查询下一行,版本号为4,即属于第三种情况,并且不存在于数组中,即该行数据可见,返回数据为name4。

由上面的情况即可以发现,两个查询事件就算同一时间查询,结果也是可能不同的。

对于查询的版本号小于min_id和大于min_id的情况,相对简单这里就不做讨论了。

而删除操作,在mysql中视作一种特殊的更新操作,会复制原来的数据,并更新状态为已删除,然后压入undo栈中。

你可能感兴趣的:(MySql事务实现的机制:MVCC)