MySQL实战 | 08 懵逼,可重复读好像失效了?

原文地址:事务到底是隔离的还是不隔离的?

我们之前学习了隔离级别和锁,在隔离级别里有一个可重复读,锁里有个行锁

  • 可重复读:事务期间,看不懂别的事务的更新;
  • 行锁:有事务 1 在更新某行数据时,若有其他事务 2 进来,会被锁住

矛盾来了:事务 2 等待结束,获取到行锁时,看到的是哪个数据呢?

按可重复读隔离级别来说,看到的应该是事务启动时的最新数据,即事务 1 修改之前的数据;

但是这样不就造成了事务 1 的修改丢失了吗?

本文脑图:https://mubu.com/doc/hyPYP01r-G

话不多说,我们先手动实验一把。

实验

我们建个表先:

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);

然后做如下操作:

实验1

说明:
1、begin/start transaction 运行后其实并不会立即启动事务,执行第一个操作 InnoDB 表的语句时才会真正启动;
2、显示启动事务:start transaction with consistent snapshot

很容易看到实验结果:

  • A 读到的值是 1
  • B 读到的值是 3

看上去 B 事务违反了可重复读隔离级别的概念,为啥呢?

原因探索

之前在学习事务隔离级别时,我们接触到了一个「视图」的概念,这个视图和我们平常接触的 view 视图并不一样。

MySQL 中的两个「视图」的区别

一、常说的视图:view

① 是用查询语句定义的虚拟表
② 在调用时执行查询,并生成结果;
③ 创建方法:create view...

二、MVCC 中的一致性视图(consistent read view)

① 用于支持隔离级别的实现:RC(Read Committed,读提交)和 RR(Repeatable Read,可重复读)
② 没有物理结构,作用是事务执行期间用来定义我能看到什么数据
③ 其中,可重复读:每个事务启动是都会重建读视图,整个事务存在期间都用这个视图;

快照是什么

很多文章都会说可重复读隔离级别下,事务启动时会生成整个库的快照

那么这个快照是什么?

我们要先了解下数据的版本问题:

其实每个事务都有一个标识 id:trx_id,是在事务启动时向存储引擎的事务系统申请的,并且是按照申请顺序严格递增的。

每行数据都有版本的概念,这里的版本其实就是修改历史,而这个修改历史是跟事务挂钩的,比如:

不同版本的数据

如图所示,一行数据被多次事务修改时,这行数据会存储多个版本,如 V1、V2、V3 等。

每个版本会记录了关联的事务 id,这里的版本并不是物理上存在的,需要根据版本号+undo log 来获取。

其实,快照就是版本号的集合。

事务启动时发生了什么

可重复读隔离级别下,事务的属性是这样的:可以看到所有已提交的更新,所有未提交的更新都不能看到。对于同一行数据,以最新一次的事务提交为数据基准。

另外,事务启动后,很可能存在其他活跃事务(启动且未提交),我们把这些活跃事务的 id 组成一个数组,并且记数组中 trx_id 最小的记为低水位,trx_id 最大的记为高水位

因此,所有的事务 id 可以分成下图这种:

事务 id
  • trx_id 在绿色部分,已提交,可见
  • 在红色部分,未提交,不可见
  • 黄色部分
    • trx_id 在数组中,未提交,不可见
    • trx_id 不在数组中,已提交,可见

举个例子

1、假设有一组事务 id:[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];

2、其中已提交(可见):[1, 2, 3, 6, 7],未开始的(不可见):[10, 11, 12],当前 id:[8]

3、那么活跃 id 数组(不可见):[4, 5, 9],高水位:9,低水位:4;

4、高低水位之间既有已提交但不在数组中的(可见):[6, 7],又有活跃的(不可见):[4, 5, 9]

实验复盘

按照上面的数组和水位的概念,我们来捋一下文章开头的实验。

首先,假设事务 A 的 id 为 10,启动后,A 的活跃数组就是 [10];

接下来是事务 B,id 为 11,启动后,B 的活跃数组是 [10, 11];

最后是 C,id 为 12,启动后,活跃数组是 [10, 11, 12];

C 处理完毕后,直接提交事务;k 的值由 1 变为 2,

此时就存储了两个版本的数据:(事务id-9, 1),(事务id-12, 2)

接下来 B 来处理,它会将 k 的值更新为 3,此时就有三个版本的数据:(事务id-9, 1),(事务id-12, 2),(事务id-11, 3)

最后 A 事务,由于 A 启动时,B、C 事务都未提交,所以它们的数据更新对于 A 来说都是看不到的,因此 A 获取到的结果是 1。

实验复盘图

更新的逻辑

不知道你注意到没有,上面的复盘中,有一个重要的点,我们没有说。

按照可重复读的逻辑,B 执行更新时,看到的 k 的值应该是 1,执行更新的话,就直接造成了 C 操作中的数据丢失。

但是事务 B 在更新时,为什么读取到了事务 C 更新的数据?

这里有一条规则,就是更新数据都是先读后写的,而这个读,只能读当前的最新值,称为当前读(current read),

即,更新数据时总是读取已经提交完成的最新版本

另外,除了 update 语句外,select 语句如果加锁,也是当前读。

读锁(S 锁,共享锁):mysql> select k from t where id=1 lock in share mode;
写锁(X 锁,排他锁):mysql> select k from t where id=1 for update;

总结

可重复读的能力是怎么实现的?

把握以下几点:

1、可重复读的核心就是一致性读(consistent read);

2、而事务更新数据的时候,只能用当前读

3、如果当前的记录的行锁被其他事务占用的话,就需要进入锁等待

另外,本文的几个重要概念:

1、一致性视图,保证了当前事务从启动到提交期间,读取到的数据是一致的(包括当前事务的修改)。

2、当前读,保证了当前事务修改数据时,不会丢失其他事务已经提交的修改。

3、两阶段锁协议,保证了当前事务修改数据时,不会丢失其他事务未提交的修改。

4、RR 是通过事务启动时创建一致性识图来实现,RC 是语句执行时创建一致性识图来实现

课后题目

我们用下面的语句初始化一个表:

mysql> CREATE TABLE `t` (
  `id` int(11) NOT NULL,
  `c` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB;
insert into t(id, c) values(1,1),(2,2),(3,3),(4,4);

你是否可以尝试制造出下面的「诡异」现象?

诡异

诡异之处在于,我们的目的是将“字段 c 和 id 值相等的行”的 c 值清零,但是发现更新语句执行成功后,c 的值并没有被清零!

答案

我们用如下操作流程就可以:

事务 A 事务 B 查询结果
start transaction with consistent snapshot;
start transaction with consistent snapshot;
select * from t; 1 2 3 4
update t set c = 5;
commit;
update t set c = 0 where id=c;
select * from t; 1 2 3 4
commit;
select * from t; 5 5 5 5

原因分析

主要原因就算当前读

事务 B 是在事务 A 之后启动的,但是事务 B 的更新提交是在 事务 A 之前。

事务 A 第一次查询时,由于可重复读,读取到的自然是 1 2 3 4。

事务 A 更新时,根据当前读规则,此时 c 的值已经是 5,不再满足更新条件 id=c,因此更新不会真正执行。

所以,事务 A 再次查询时,获取到的仍然是 1 2 3 4,事务 A 提交后,再查询时,获取到的自然是最新的数据了。


你的关注是对我最大的鼓励!

最近搜集到传智播客 2018 最新 Python 和 Java 教程!关注本公众号,后台回复「2018」即可获取下载地址。

公众号提供CSDN资源免费下载服务!


©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349

推荐阅读更多精彩内容

  • 当一个系统访问量上来的时候,不只是数据库性能瓶颈问题了,数据库数据安全也会浮现,这时候合理使用数据库锁机制就显得异...
    初来的雨天阅读 3,560评论 0 22
  • 观其大纲 第1章 MySQL体系结构和存储引擎第2章 InnoDB存储引擎第3章 文件第4章 表第5章 索引与算法...
    周少言阅读 6,887评论 0 33
  • 一、事务 1、事务四要素:ACID 对于事务,我之前的理解是很粗糙的,不就是为了保证操作的原子性么?一般订单系统或...
    张伟科阅读 1,285评论 0 5
  • ORA-00001: 违反唯一约束条件 (.) 错误说明:当在唯一索引所对应的列上键入重复值时,会触发此异常。 O...
    我想起个好名字阅读 5,256评论 0 9
  • 多年前有一部戏很好叫《成长的烦恼》,今天在回头想,我会好奇的问你,成长是什么?我把自己关在屋子思考,一不小...
    乜石头阅读 193评论 0 1