前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >分析一个MySQL并发事务示例

分析一个MySQL并发事务示例

作者头像
dys
发布2019-03-07 11:01:09
8730
发布2019-03-07 11:01:09
举报
文章被收录于专栏:性能与架构性能与架构

示例

有这样一张表:

现在有3个事务 A B C 并发执行:

事务A、事务B的select的结果是什么呢?

使用 ”start transaction with consistent snapshot;“ 语句的目的是马上启动事务,”begin/start transaction“命令不是一个事务的起点,而是在执行到他们之后的第一个操作 InnoDB 表的语句后事务才开始。

下面我们实际执行以下看看结果,先建表插入数据:

代码语言:javascript
复制
mysql> CREATE TABLE `t` (
  `id` int(11) NOT NULL,
  `k` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB;

insert into t(id, k) values(1,1),(2,2);

然后开3个mysql client终端,分别代表 A B C,执行:

代码语言:javascript
复制
# client A
start transaction with consistent snapshot;

# client B
start transaction with consistent snapshot;

# client C
update t set k=k+1 where id=1;

# client B
update t set k=k+1 where id=1;
select k from t where id=1; # 3

# client A
select k from t where id=1; # 1
commit;

# client B
commit;

结果:

  • 事务B的select语句结果是 3
  • 事务A的结果是 1

如果你猜对了,也知道是为什么,可以休息了 : ),如果没猜对,那么咱们就一起分析一下。

分析

1. 背景知识

MySQL 默认的事务隔离级别是可重复读,事务在启动时给整个库拍了个快照。

如果一个库有100G,那快照多慢啊,实际快照不是做数据复制,而是基于数据版本日志。

InnoDB里每个事务都有一个唯一的事务ID(transaction id),事务启动时申请,按申请顺序严格递增

每行数据是有多个版本的,每次事务更新数据的时候,都会生成一个新的版本,并且记录上这个版本是哪个事务ID干的。所以,每行记录都可能有多个版本,每个版本有自己的 row trx_id

虚线中是同一行数据的4个版本,当前最新版本是 V4,k 值为22,是ID为25的事务更新的。V1 V2 V3 并不是物理存在的,需要的时候可以通过当前版本和 undo log 计算出来。

根据”可重复读“的定义,一个事务启动时,能够看到所有已经提交的事务的结果,事务执行期间,其他事务的更新就不可见了。

所以,就相当于一个事务启动时发出声明:”以我启动时间为准,我只认启动之前的数据版本,如果是我启动之后生成的,我就找之前的数据版本,直到我认可的版本“。

InnoDB 会为每个事务构造一个数组,用来保存在这个事务启动的瞬间正在活跃(已启动但还未提交)的所有事务ID。

例如 transaction id 为 15 的事务启动时,还有事务9、事务12 活跃,那么事务15就有一个数组:[9,12,15],通过这个数组我们可以明确几件事:

  1. < 9 的事件肯定都可见,因为都已经执行完成了。
  2. > 15 的事件肯定都不可见,因为这些事件是后来的。
  3. 这个数组中,除了自己以为的其他事务的更改结果肯定是不可见的,因为他们在我启动的时候还没提交呢。
  4. > 9 并且 < 15 的事件中,不在这个数组中的,肯定是可见的,因为他们在我启动时都提交完成了,例如事务10,ID小于我(15),而且在我启动时不是活跃的,说明执行完成了,那么对我来讲他就是可见的。

所以,我们现在知道了,InnoDB 利用 所有数据都有多个版本 这个特性,实现了秒建快照的能力。

2. 示例分析

现在我们回到之前的示例,看看执行过程。

假设:

  1. 事务A 开始前,只有一个活跃事务 99
  2. 事务 A B C 的版本号是 100、101、102,当前系统只有这4个事务
  3. 3个事务开始之前,(1,1)这行数据的 row trx_id = 90

这样,视图数组如下:

  • 事务A -> [99, 100]
  • 事务B -> [99, 100, 101]
  • 事务C -> [99, 100, 101, 102]
2.1事务A的逻辑

第一个有效更新时事务C,这个数据的最新版本的 row trx_id 是 102,90 那个版本成为了历史版本。

第二个有效更新时事务B,这个数据的最新版本的 row trx_id 变成了 101。

然后事务A来读数据了,他的视图数组是 [99,100],事务A的读取流程是这样的:

  • 找到当前版本,一看 row trx_id = 101,比自己视图数组的最大值还大,不可见
  • 接着找上一个版本,一看 row trx_id = 102,还是不可见
  • 再找上一个版本,row trx_id = 90,比自己视图数组的最小值还小,终于可见了

所以,事务A的查询结果就是 1

2.2 事务B的逻辑

对于事务B,我们会有一个疑问,根据一致性读规则,结果不对啊,事务B的视图数组是先生成的,事务C后提交的,事务B执行 update 时,不是应该基于 (1,1)吗?怎么会基于(1,2)呢?

这涉及到另一个原则:更新数据时要先读后写,此时读的是”当前版本“,称为 ”当前读(current read)“。

因为更新数据时不能再在历史版本上更新,否则事务C的更新就丢失了,所以事务B的 update 是在(1,2)基础上操作的。

事务B执行 select 时,一看数据的当前版本是 101,是自己更新的,可以直接使用,所以查询结果就是 3 了。

总结

可重复读隔离级别的核心:

  • 读数据是一致性读
  • 更新数据是当前读
  • 如果当前记录的行锁被其他事务占用,进入等锁状态

内容整理自阿里技术专家丁奇的专栏《MySQL实战45讲》

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-01-12,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 JAVA高性能架构 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 示例
  • 分析
    • 1. 背景知识
      • 2. 示例分析
        • 2.1事务A的逻辑
        • 2.2 事务B的逻辑
    • 总结
    相关产品与服务
    云数据库 MySQL
    腾讯云数据库 MySQL(TencentDB for MySQL)为用户提供安全可靠,性能卓越、易于维护的企业级云数据库服务。其具备6大企业级特性,包括企业级定制内核、企业级高可用、企业级高可靠、企业级安全、企业级扩展以及企业级智能运维。通过使用腾讯云数据库 MySQL,可实现分钟级别的数据库部署、弹性扩展以及全自动化的运维管理,不仅经济实惠,而且稳定可靠,易于运维。
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档