MySQL的并发控制MVCC

看下如下2个demo,关闭了自动事务,select和update语句会自动给users表加上S锁(共享锁)和X锁(排它锁)。S锁和X锁是互斥的,按理说先执行的事务未commit或者rollback之前,另一个事务是无法对users表加互斥锁进行操作的,但是这里两个demo却可以正常执行。
demo1:

transaction1:
      set session autocommit=off;
      update users set lastUpdate=now() where id =1;//加x锁,使用记录锁Record锁
transaction2:
      select *  from users where id > 1;//未锁住,可以执行是正常的。
      select * from users where id= 1;//按理说条数据行应该锁住,不能加S锁才对啊,为什么可以加S锁呢?

demo2:

transaction1:
     begin
      select * from users where id= 1;
transaction2:
      update users set lastUpdate=now() where id =1;
 transaction1:
      select * from users where id= 1;

一 MVCC
MVCC (Mutil-Version Concurrency ControL),多版本并发控制。并发访问(read/write)数据库时,对正在事务内处理的数据做多版本的管理,避免因写操作堵塞而引发读操作的并发问题。

建一张简单的表,有如下三个字段,我们看到的结构如下。

   主键        姓名         年龄
---------------------------------
|  id     |    name     |  age  |
----------------------------------
|         |             |        |
----------------------------------

实际上MySQL底层还会帮助我们多建立2个字段:trx_id 和 roll_pointer,它们的取值均来自于全局事务ID生成器。
trx_id 用于记录

   主键        姓名         年龄     数据行版本号     删除版本号
-------------------------------------------------------------
|  id     |    name     |  age  |    trx_id    | roll_pointer
-----------------------------------------------------------
|         |             |       |              |
------------------------------------------------------------

我们对表插入数据:

begin;//自动获取当前的全局事务ID,假设这里ID=1
insert into users (name,age) values('tom',18);
insert into users (name,age) values('lily',18);
commit;

那么sql执行后的实际上的表内容如下:

   主键        姓名         年龄     数据行版本号   删除版本号
-------------------------------------------------------------
|  id     |    name     |  age  |    trx_id    | roll_pointer
-----------------------------------------------------------
|   1     |    tom      |  18   |       1      |    null
------------------------------------------------------------
|   2     |    lily     |  18   |       1      |    null
------------------------------------------------------------

删除数据

begin;//自动获取当前的全局事务ID,假设这里ID=22
delete users where id=2;
commit;

那么sql执行后的实际上的表内容如下:

   主键        姓名         年龄     数据行版本号   删除版本号
-------------------------------------------------------------
|  id     |    name     |  age  |    trx_id    | roll_pointer
-----------------------------------------------------------
|   1     |    tom      |  18   |       1      |    null
------------------------------------------------------------
|   2     |    lily     |  18   |       1      |     22
------------------------------------------------------------

看下修改操作:

begin;//自动获取当前的全局事务ID,假设这里ID=33
update users set age =20 where id = 1;
commit;

那么sql执行后的实际上的表内容如下:

   主键        姓名         年龄     数据行版本号   删除版本号
-------------------------------------------------------------
|  id     |    name     |  age  |    trx_id    | roll_pointer
-----------------------------------------------------------
|   1     |    tom      |  18   |       1      |    33
------------------------------------------------------------
|   2     |    lily     |  18   |       1      |    22
------------------------------------------------------------
|   1     |    tom      |  19   |      33      |    null
------------------------------------------------------------

update过程相当于如下两步:
1.将命中的数据行复制一份修改,包括业务数据age=19和trx_id=33
2.原来的数据roll_pointer标记为当前事务ID33。

基于前面的步骤,看下查询操作。

begin;//自动获取当前的全局事务ID,假设这里ID=44
select * from users;
commit;

查询规则:
1.查找数据行版本号早于当前事务版本的数据行,即版本号小于当前事务id的数据行。
这样可以保证当前事务读取的数据行,要么是事务开始前已经存在的,要么是事务自身插入后者修改过的。
2.查找删除版本号要么为null,要么要么大于当前事务版本号的数据行。
这样可以保证数据行在事务开启前没有被删除。
根据规则1,三条记录都符合,根据规则2只有第三条数据符合,查询结果如下:

   主键        姓名         年龄     数据行版本号   删除版本号
-------------------------------------------------------------
|  id     |    name     |  age  |    trx_id    | roll_pointer
-----------------------------------------------------------
|   1     |    tom      |  19   |      33      |    null
------------------------------------------------------------

UndoLog
事务开始之前,首先将命中的数据备份到一个undolog中。undolog是为了实现事务原子性而产生的,当事务过程中出现了异常或者执行了rollback,就可以利用undolog中的备份将数据恢复到事务开始之前的状态。

InnoDB存储引擎利用undolog中的备份数据作为快照,供其他并发事务进行读操作。
MySQL的并发控制MVCC_第1张图片
快照读
sql读取的数据是快照版本,也就是历史版本,普通的select就是快照读,InnoDB快照读由cache + undo两部分组成。快照读通过MVCC解决幻读问题。

当前读:
sql读取的数据是最新版本,UPDATE,DELETE,INSERT,SELECT …LOCK IN SHAR MODE,SELECT … FOR UPDATE都是当前读。通过临键锁NEXT-KEY锁机制解决幻读问题。

Redo log
事务操作数据时,将最新的数据备份到redo log中。redo log是为了实现事务的持久性而出现的产物。
MySQL的并发控制MVCC_第2张图片

你可能感兴趣的:(数据库)