MVCC多版本并发控制

  • 快照读与当前读
  • 隔离级别
  • 隐藏字段,undo log 版本链
    • 隐藏字段trx_id
    • 版本链
  • read view
  • 举例说明
    • read committed(读已提交)隔离级别下
    • repeatable read(可重复读)隔离级别下
    • innodb如何解决幻读
  • 总结

并发问题的解决办法:加锁或mvcc
读操作用mvcc,写操作加锁,读写不冲突,并发性好。mvcc的实现就是依赖于隐藏字段、undo log(多版本)、read view(控制)。
MySQL的存储引擎中,只有innodb支持mvcc。

快照读与当前读

不加锁的简单select读都是快照读,读的是历史数据。需要加锁的场景,读取的是记录的最新版本,加锁的select、对数据进行增删改都会进行当前读

隔离级别

事务有四个隔离级别,可能存在三种并发问题:

MySQL中默认的隔离级别为可重复读,它实际上解决了脏读、不可重复读和幻读,但并不是串行化。

隐藏字段,undo log 版本链

隐藏字段trx_id

trx_id :每次一个事务对某条聚簇索引记录进行改动时,都会把该事务的事务id赋值给trx_id 隐藏列。
roll_pointer :每次对某条聚簇索引记录进行改动时,都会把旧的版本写入到undo日志中,然后这个隐藏列就相当于一个指针,可以通过它来找到该记录修改前的信息。
例如:
有一student表,id = 1,name = ‘张三’,class = ‘一班’
假设插入该记录的事务id为8,此条记录的结构如下:

版本链

每次对记录进行改动,都会记录一条undo日志,每条undo日志也都有一个roll_pointer 属性( INSERT 操作对应的undo日志没有该属性,因为该记录并没有更早的版本),可以将这些undo日志都连起来,串成一个链表,就是版本链。


对该记录每次更新后,都会将旧值放到一条undo日志中,就算是该记录的一个旧版本,随着更新次数的增多,所有的版本都会被roll_pointer 属性连接成一个链表,我们把这个链表称之为版本链,版本链的头节点就是当前记录最新的值。

read view

readview就是事务A在使用MVCC机制进行快照读操作时产生的读视图。当事务启动时,会生成数据库系统当前的一个快照,innodb为每个事务构造了一个数组,用来记录并维护系统当前活跃(begin了但未commit)事务的id。

举例说明

mvcc只在read committed和repeatable read两个隔离级别下工作。

read committed(读已提交)隔离级别下

read committed:每次读取数据前都生成一个readview。
事务id的分配:如果是增删改行为,系统自动递增分配事务id;如果是查询行为,事务id为0.
现在有两个事务id 分别为10 、20 的事务在执行:

# Transaction 10
BEGIN;
UPDATE student SET name="李四" WHERE id=1;
UPDATE student SET name="王五" WHERE id=1;
# Transaction 20
BEGIN;
# 更新了一些别的表的记录
...

student表中id为1的记录得到的版本链表如图:

此时有个事务隔离级别为read committed,进行如下操作:

begin;
select * from student where id = 1;

因为transaction 10 未提交,所以这个事务查出来的结果是“1 张三 一班 8”这条记录。接下来解释原因:

  1. 在执行select操作时,生成read view,针对当前操作的快照,快照中记录了信息:creator_trx_id = 0(查询操作事务id为0)、trx_ids列表内容[10,20](id为10和20的两个事务活跃)、up_limit_id = 10(小的事务是10)、low_limit_id = 21(最大的事务不会超过20);
  2. 从版本链中挑选可见的记录,最新的是“王五”的记录,trx_id = 10,与creator_trx_id = 0不等,而且10在trx_ids列表中,表明10这个事务还没有提交,肯定不能查出;
  3. 接着找下一个版本,“李四”的trx_id 还是 10,也不行;
  4. 再往下找到“张三”trx_id = 8,不在活跃的事务列表里,一定是之前已经提交的事务,那么就把“张三”这条事务读出来。

接下来,把transaction 10的事务提交,

# Transaction 10
BEGIN;
UPDATE student SET name="李四" WHERE id=1;
UPDATE student SET name="王五" WHERE id=1;
COMMIT;

把transaction 20的事务中更新一下表student 中id 为1 的记录:

# Transaction 20
BEGIN;
# 更新了一些别的表的记录
...
UPDATE student SET name="钱七" WHERE id=1;
UPDATE student SET name="宋八" WHERE id=1;

此时版本链变成了这样:

接下来,事务又读了一次:

begin;
# select 1: 10 20 未提交
select * from student where id = 1;        # 张三
# select 2: 10提交,20未提交
select * from student where id = 1;        # 王五
  1. **在read committed隔离级别下,每读一次,生成一个read view。**所以第二次select的时候,又生成了一个read view。该readview的trx_ids列表内容剩[20],up_limit_id = 20,low_limit_id = 21,creator_trx_id = 0;
  2. 从版本链中挑选,最新版本“宋八” trx_id = 20,还活跃着,就跳到下一个版本
  3. “钱七”的trx_id = 20,跳到下一版本
  4. "王五"的trx_id = 10,是提交过的,是生成read view之前的事务,所以查出这条记录。

repeatable read(可重复读)隔离级别下

只会在第一次查询时生成read view,之后的查询不会重复生成。
还是上面的例子:

# Transaction 10
BEGIN;
UPDATE student SET name="李四" WHERE id=1;
UPDATE student SET name="王五" WHERE id=1;
# Transaction 20
BEGIN;
# 更新了一些别的表的记录
...


此时有个事务隔离级别为read committed,进行如下操作:

begin;
select * from student where id = 1;

同上,此时查到的是“张三”记录。read view中:creator_trx_id = 0(查询操作事务id为0)、trx_ids列表内容[10,20](id为10和20的两个事务活跃)、up_limit_id = 10(小的事务是10)、low_limit_id = 21(最大的事务不会超过20);

提交transaction 10,到transaction 20中更新表student,

# Transaction 20
BEGIN;
# 更新了一些别的表的记录
...
UPDATE student SET name="钱七" WHERE id=1;
UPDATE student SET name="宋八" WHERE id=1;


在这个场景下,刚才读过的事务现在又读了一次:

begin;
# select 1: 10 20 未提交
select * from student where id = 1;        # 张三
# select 2: 10提交,20未提交
select * from student where id = 1;        # 张三

这时不再生成新的read view,还是一开始的read view。creator_trx_id = 0(查询操作事务id为0)、trx_ids列表内容[10,20](id为10和20的两个事务活跃)、up_limit_id = 10(小的事务是10)、low_limit_id = 21(最大的事务不会超过20);

  1. 版本链中最新的版本“宋八” trx_id = 20,是活跃的事务,跳过;
  2. “钱七” trx_id = 20,活跃的事务,跳过; (这两个是transaction 20中的更新,未提交)
  3. “王五” trx_id = 10,活跃的事务,跳过; (这是transaction 10 中的更新,已经提交,但此时的read view是首次select时生成的,所以认为transaction 10 还在活跃)
  4. “李四” trx_id = 10,活跃的事务,跳过;
  5. “张三” trx_id = 8,不在trx_ids列表中,查出。

所以说,只要查询事务没有提交,用的都是第一次select时生成的read view。

innodb如何解决幻读

幻读:student表中有一条id=1的记录,事务A开始进行第一次查询,结果显示id=1,现有事务B对student表进行更新操作,添加了id=2,id=3的两条记录并提交,接下来事务A再进行第二次查询,结果显示id=1,id=2,id=3,同一事务A前后两次查询操作的返回结果不相同,这就是出现了幻读。
在repeatable read隔离级别下,read view只在事务第一次进行查询操作时生成,此后的查询操作(只要不commit)仍用这个视图,所以就算事务B提交了更新,read view并不同步更新,trx_ids列表中仍有事务B的trx_id,认为事务B处于活跃状态,跳过更新的那条记录。

总结

MVCC = 隐藏字段 trx_id + undo log 版本链 + read view 快照

READ COMMITTD 在每一次进行普通SELECT操作前都会生成一个ReadView。
REPEATABLE READ 只在第一次进行普通SELECT操作前生成一个ReadView,之后的查询操作都重复使用这个ReadView。

说明:执行delete语句或更新主键的update语句并不会立即把对应的记录完全从页面中删除,而是执行一个所谓的delete mark操作,相当于只是对记录上打了一个删除标志位,这就是为MVCC服务的,因为可能需要回滚。

【SQL】MVCC 多版本并发控制相关推荐

  1. 【MVCC多版本并发控制】MVCC 机制的原理及实现,什么是MVCC,多版本并发控制

    什么是 MVCC MVCC (Multiversion Concurrency Control) 中文全程叫多版本并发控制,是现代数据库(包括 MySQL.Oracle.PostgreSQL 等)引擎 ...

  2. MYSQL专题-MVCC多版本并发控制

    MVCC,全称Multi-Version Concurrency Control,即多版本并发控制.MVCC是一种并发控制的方法,一般在数据库管理系统中,实现对数据库的并发访问,在编程语言中实现事务内 ...

  3. MySQL MVCC多版本并发控制(脏读和不可重复读解决原理)

    文章目录 一.MVCC概念 二.MVCC应用于已提交读隔离级别 1. 解决脏读 2. 无法解决不可重复读 3. 无法解决幻读 三.MVCC应用于可重复读隔离级别 1. 解决脏读 2. 解决不可重复读 ...

  4. MySQL 高级 —— MVCC 多版本并发控制

    引言 MySQL的大多数事务型存储引擎实现的都不是简单的行级锁.基于提升并发性能的考虑,它们一般都同时实现了多版本并发控制--MVCC.包括其他数据库如Oracle等,由于MVCC并没有一个统一的实现 ...

  5. 并发控制:(三)MVCC 多版本并发控制

    1.概述: 定义:Multiversion concurrency control, is a concurrency control method commonly used by database ...

  6. MySQL数据库MVCC多版本并发控制简介

          MVCC (Multiversion Concurrency Control),即多版本并发控制技术,它使得大部分支持行锁的事务引擎,不再单纯的使用行锁来进行数据库的并发控制,取而代之的是 ...

  7. MySQL第一讲 一遍让你彻底掌握MVCC多版本并发控制机制原理

    Mysql在可重复读隔离级别下,同样的sql查询语句在一个事务里多次执行查询结果相同,就算其它事务对数据有修改也不会影响当前事务sql语句的查询结果.这个隔离性就是靠MVCC(Multi-Versio ...

  8. 【MySQL】MVCC多版本并发控制(重点:MVCC实现原理之ReadView)

    [大家好,我是爱干饭的猿,本文重点介绍MySQL的MVCC概念.快照读与当前读.MVCC实现原理之ReadView.隐藏字段.Undo Log版本链. 后续会继续分享MySQL和其他重要知识点总结,如 ...

  9. fetch first mysql_MySQL多版本并发控制机制(MVCC)源码浅析

    MySQL多版本并发控制机制(MVCC)-源码浅析 前言 作为一个数据库爱好者,自己动手写过简单的SQL解析器以及存储引擎,但感觉还是不够过瘾.<>诚然讲的非常透彻,但只能提纲挈领,不能让 ...

最新文章

  1. jquery高版本全选与全部选无法正常工作
  2. 【PC工具】建议收藏!一个有N多日常生活学习办公小工具的神奇网站,推荐在线工具网站...
  3. WSL(windows subsystem for linux)安装错误:安装过程中遇到错误,但可以继续安装。组件: ‘WSL 内核‘ 错误代码: 0x80072f78解决方法
  4. 了解一下C++输入和输出的概念
  5. 图书馆管理系统用户端心得
  6. Caused by: Parent package is not defined: json-default - [unknown location]
  7. 齐浩亮 计算机科学与技术,齐浩亮
  8. 用单张2D图像重构3D场景
  9. Linq lambda表达式经验总结
  10. css如何改变横线<hr/>标签的颜色
  11. matlab简单分析频域滤波和时域滤波
  12. 886n虚拟服务器ip,tl-wr886n怎么配置ip带宽控制
  13. 判断设备是否是 iphone5
  14. 加密、解密、数字签名和数字证书概念详解
  15. 5红5绿6蓝穿手链c语言,礼仪习题库(含答案)
  16. mysql 设置多个主码
  17. web前端程序员求职时该如何写简历
  18. JS-BOM对象概叙
  19. C语言实现三子棋及多子棋的制作(初学者)
  20. 物联网战略的成败在于开放性

热门文章

  1. 水表空转问题分析与解决——摘录
  2. 干货 | 浅谈机器人强化学习--从仿真到真机迁移
  3. 宽字节 多字节 单字节 的问题
  4. 微商城页面搭建教程你学到了吗?
  5. Opencv进行人脸检测(第三版改进)
  6. 计算机英语新词的认知语义阐释论文,汉语言文学-网络流行语的认知语义阐释-毕业论文格式范文(2)-优度********网...
  7. 搞笑音乐:人在江湖漂
  8. 给学弟学妹们写了个 15W 字的图解操作系统!
  9. 从1G说到5G:科技改变生活
  10. 51.性能调优之广播大变量