深度解析 MySQL 事务、隔离级别和 MVCC 机制:构建高效并发的数据交响乐(二)

本文涉及的产品
云数据库 RDS MySQL Serverless,0.5-2RCU 50GB
简介: 深度解析 MySQL 事务、隔离级别和 MVCC 机制:构建高效并发的数据交响乐(二)

MVCC

MVCC 全称 Multi-Version Concurrency Control,多版本并发控制,主要是为了提高数据库的并发性能

平时,在同一行数据上同时发生读写请求时,会上锁阻塞住,但 MVCC 提供更好的方式去处理读-写请求,可以做到在发生读-写请求冲突时不用加锁

这个读是快照读,不是当前读,当前读是一种加锁的操作,是悲观锁 > FOR UPDATE

前面在介绍 隔离级别 时,说到了 MySQL 在 REPEATABLE READ 隔离级别下,可以很大程度上避免幻读问题的发生,从以下几个概念及实操来说明是如何去避免的

版本链

当对一条数据并发执行多次操作时,对该条数据会形成版本链

对于使用 InnoDB 存储引擎的表来说,它的聚簇索引记录中包含了两个必要的隐藏列,不包括 row_id

row_id 并不是必要的,在创建的表中有主键或非 NULL 的 UNIQUE 键时,row_id 就是主键或唯一键,若没有主键或唯一键,会默认生成 row_id

必要的两个隐藏列,如下:

  1. trx_id:一个事务对某条聚簇索引记录进行改动时,都会把该事务的 id 赋值给 trx_id 隐藏列
  2. roll_pointer:在对某条聚簇索引记录进行改动时,都会把旧的版本写入到 undo 日志中,然后 roll_pointer 这个隐藏列就相当于一个指针,可以通过它来溯源该记录修改前的信息

undo 日志:为了实现事务的原子性操作,InnoDB 存储引擎在实际进行 DML 操作记录时,都先要把对应的 undo 日志记下来。一般对一条记录进行一次改动,就对应一条 undo 日志,但在某些更新操作中,也可能会对应两条 undo 日志;一个事务在执行过程中可能会新增、删除、更新若干条记录,也就是说会记录很多条 undo 日志,这些 undo 日志会从 0 开始编号,依此按顺序生成:1、2、…、N,此编号被称之为 undo no

undo 日志是 MySQL 三大日志其中之一,还包含了 redo log、bin log,日志这方面的内容后续文章再详细分析

为了说明【MySQL 在 REPEATABLE READ 隔离级别下,可以很大程度上避免幻读问题的发生】此问题,创建一张演示表,如下:

create table technology_column(
  id BIGINT(10) not null primary key auto_increment COMMENT '主键',
  category_name varchar(30) not null COMMENT '专栏名称'
) Engine=InnoDB CHARSET=utf8 COMMENT '技术专栏表';

往这张表中插入一条数据,如下:

INSERT INTO technology_column VALUES(1, 'Spring');

假设插入该条数据的事务 id > trx_id = 80,那么此条记录的示意图如下:

假设之后有两个事务:trx_id 分别为 100、120,对这条记录进行了 UPDATE 操作,操作流程如下:

对记录每次进行改动,都会记录一条 undo 日志,每条 undo 日志都有一个 roll_pointer 属性

(INSERT INTO 操作对应的 undo 日志没有该属性,因为该记录没有更早的版本)可以将这些 undo 日志连起来,串成一个链表,如上图右侧所示~

对 INSERT INTO 操作后的记录,每次更新后,都会将旧值放到一条 undo 日志中,就当是该记录的一个旧版本,便于作事务回滚、数据溯源,随着更新的次数增多,所有的版本都会被 roll_pointer 属性连接成一个链表,将这个链表称为版本链,版本链的头节点就是当前记录最新的值;另外,每个版本中还包含了生成该版本对应的事务 id

基于此,利用该记录的版本链来控制并发事务同时访问该记录的行为,那么这种机制就称之为多版本并发控制 MVCC

ReadView

读取视图 > 作用于 SQL 查询语句

对于使用 READ UNCOMMITTED 读未提交隔离级别的事务来说,由于可以读取到未提交的事务修改过的信息,所以直接读取记录的最新版本即可,由此读未提交就会出现脏读、不可重复读、幻读

对于使用 SERIALIZABLE 可串行化读隔离级别的事务来说,InnoDB 采用加锁的方式来访问记录,当事务正在执行时,其他事务就会阻塞住直到前面的事务提交或回滚后才会执行,所以不会出现脏读、不可重复读、幻读

引入版本链的机制主要是为了解决:已提交读、可重复读的事务隔离级别

对于使用 READ COMMITTED、REPEATABLE READ 隔离级别事务来说,都必须保证读取到的数据是已经事务已提交修改过的记录,也就是说:假如事务已经修改了记录但尚未提交,是不能直接读取到最新版本记录的

核心问题:READ COMMITTED、REPEATABLE READ 隔离级别在不可重复读、幻读上的区别是从何而来,基于前面所介绍的版本链,主要关键是需要判断这两种级别在版本链中哪个版本是当前事务可见的,为此 InnoDB 提出了 ReadView 概念

ReadView 主要包含了四个比较重要的内容,如下:

  1. m_ids:表示在生成 ReadView 时当前系统正在活跃的读写事务的事务 id 集合
  2. min_trx_id:表示在生成 ReadView 时当前系统中获取的读写事务中的最小事务 id,也就是 m_ids 中的最小值
  3. max_trx_id:表示在生成 ReadView 时当前系统中应该分配给下一个事务的 id 值

max_trx_id 并不是 m_ids 集合中的最大值,事务 id 是递增分配的;比如:现在有 id > 1、2、3 三个活跃事务,之后 id=3 的事务提交了,那么新的读事务在生成 ReadView 时, m_ids 集合中还有 1、2,min_trx_id 值就为 1,max_trx_id 值就为 4

  1. creator_trx_id:表示生成该 ReadView 读取视图的事务 id

下面来具体介绍,READ COMMITTED、REPEATABLE READ 隔离级别是如何分别处理脏读、不可重复读、幻读问题的.

在 MySQL 中,READ COMMITTED、REPEATABLE READ 隔离级别非常大的一个区别就是它们生成 ReadView 的时机不同

READ COMMITTED

READ COMMITTED 隔离级别的事务在每次查询开始时都会生成一个 ReadView

以上面的 technology_column 表为例,现在只有一条事务 id 为 80 插入的一条记录

比如:现有系统中有两个事务 > Trx_id 100、120 在执行,事务 > Trx_id 100、120 SQL 语句如下:

Trx_id-100、120 Begin;
Trx_id-100、120:select * from technology_column where id = 1;
Trx_id-100:update technology_column set category_name ='MySQL' where id = 1;   
Trx_id-100:update technology_column set category_name ='Redis' where id = 1;   
Trx_id-100:Commit;
Trx_id-120:update technology_column set category_name ='分布式' where id = 1;   
Trx_id-120:update technology_column set category_name ='Linux' where id = 1;   
Trx_id-120:Commit;

脏读问题

Trx_id-100:Commit; 语句执行之前,technology_column.id =1 记录得到的版本链表,如下所示:

# 查询语句
select * from technology_column where id = 1;

以上使用 READ COMMITTED 隔离级别的事务,Trx_id 100、120 的事务均未提交,所以此时查询的数据仍然为 Spring!在 Trx_id-100:Commit; 语句执行之前,整个的执行过程如下:

  1. 在执行语句时,会先生成一个 ReadView
  2. ReadView 活跃集合 m_ids 内容为 100,120,min_trx_id 为 100,max_trx_id 为 121,creator_id 为 0
  3. 从版本链中挑选可见的记录,从上图中可以看出,最新版本的 category_name 值内容为 Redis;该版本的 trx_id 为 100,在 m_ids 集合内,所以不符合可见性要求

trx_id 属性值在 ReadView 中 min_trx_id、max_trx_id 之间,说明创建 ReadView 时生成该版本的事务还是活跃的,该版本不可以被访问;若不在 min_trx_id、max_trx_id 之间,说明创建 ReadView 时生成该版本的事务已经被提交,该版本才可以被访问

  1. 通过 Roll_ptr 指针跳到下一版本继续访问,下一个版本 category_name 值内容为 MySQL,该版本的 trx_id 为 100,仍然在 m_ids 集合内,也不符合可见性要求
  2. 通过 Roll_ptr 指针跳到下一版本继续访问,下一个版本 category_name 值内容为 Spring,该版本的 trx_id 为 80,小于 ReadView 中 min_trx_id 值,所以这个版本是符合要求的,最后返回给客户端的版本就是这条 category_name 值内容为 Spring 的记录

所以有这种机制存在,就不会发生脏读问题!因为会去判断活跃的事务版本,必须是不在活跃中的事务版本才能使用,也就不可能读到没有 commit 提交的记录


相关实践学习
基于CentOS快速搭建LAMP环境
本教程介绍如何搭建LAMP环境,其中LAMP分别代表Linux、Apache、MySQL和PHP。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助     相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
相关文章
|
2天前
|
人工智能 Go 调度
掌握Go并发:Go语言并发编程深度解析
掌握Go并发:Go语言并发编程深度解析
|
1月前
|
安全 Java 数据库连接
jdbc解析excel文件,批量插入数据至库中
jdbc解析excel文件,批量插入数据至库中
21 0
|
5天前
|
SQL 存储 关系型数据库
数据库开发之mysql前言以及详细解析
数据库开发之mysql前言以及详细解析
14 0
|
16天前
|
存储 关系型数据库 MySQL
MySQL引擎对决:深入解析MyISAM和InnoDB的区别
MySQL引擎对决:深入解析MyISAM和InnoDB的区别
31 0
|
4天前
|
负载均衡 关系型数据库 MySQL
MySQL-Proxy实现MySQL读写分离提高并发负载
MySQL-Proxy实现MySQL读写分离提高并发负载
|
5天前
|
Java Spring
Javaweb之SpringBootWeb案例之事务进阶的详细解析
Javaweb之SpringBootWeb案例之事务进阶的详细解析
11 0
|
15天前
|
存储 JSON JavaScript
「Python系列」Python JSON数据解析
在Python中解析JSON数据通常使用`json`模块。`json`模块提供了将JSON格式的数据转换为Python对象(如列表、字典等)以及将Python对象转换为JSON格式的数据的方法。
31 0
|
29天前
|
canal 消息中间件 关系型数据库
【分布式技术专题】「分布式技术架构」MySQL数据同步到Elasticsearch之N种方案解析,实现高效数据同步
【分布式技术专题】「分布式技术架构」MySQL数据同步到Elasticsearch之N种方案解析,实现高效数据同步
79 0
|
29天前
|
缓存 算法 关系型数据库
MySQL是如何进行并发控制的?
MySQL 采用了多种并发控制机制来管理并发访问,确保数据的一致性和正确性。以下是一些常见的并发控制方法: 1. **锁机制**:MySQL 使用锁来控制对数据的并发访问。常见的锁类型包括共享锁(Shared Lock)和排他锁(Exclusive Lock)。共享锁用于读取数据,允许多个事务同时读取,但阻止其他事务进行写操作。排他锁用于写入数据,会阻塞其他事务的读写操作。 2. **隔离级别**:MySQL 支持不同的隔离级别,如读已提交(Read Committed)、可重复读(Repeatable Read)和串行化(Serializable)等。隔离级别决定了并发事务之间的可见性和冲突
|
30天前
|
关系型数据库 MySQL 测试技术
面试-MySQL的四种事务隔离级别
面试-MySQL的四种事务隔离级别
21 0

推荐镜像

更多