浅谈MySQL的隔离级别

前言

说道数据库就避免不了两个内容,索引与隔离级别。以下内容为两篇介绍这个内容比较好的文章:
美团点评团队:Innodb中的事务隔离级别和锁的关系
张洋先生的:MySQL索引背后的数据结构及算法原理

关于MySQL的隔离级别,介绍的文章有许多,但是多数都是止于介绍,所以每次看完都只是有个概念上的认识,但是具体的理解还需要个人去体会。所以写下这篇博文来记录下点滴与理解。

说明:为了能够更加形象的去理解以下的内容,特别推荐去亲自进行尝试。比如开两个MySQL的客户端应该是最简单的方式,然后利用BEGIN,COMMINT等方式进行模拟

四种隔离级别

隔离级别 脏读(Dirty Read) 不可重复读(NonRepeatable Read) 幻读(Phantom Read) 说明
未提交读(Read uncommitted/RU) 可能 可能 可能 允许脏读,也就是可能读取到其他会话中未提交事务修改的数据
已提交读(Read committed/RC) 不可能 可能 可能 只能读取到已经提交的数据。Oracle等多数数据库默认都是该级别 (不重复读)
可重复读(Repeatable read/RR) 不可能 不可能 可能 可重复读。在同一个事务内的查询都是事务开始时刻一致的,InnoDB默认级别。在SQL标准中,该隔离级别消除了不可重复读,但是还存在幻象读
可串行化(Serializable) 不可能 不可能 不可能 完全串行化的读,每次读都需要获得表级共享锁,读写相互都会阻

为了理解上面的内容,我们首先需要理解下脏读不可重复读幻读的概念。

  • 脏读
    脏读概念的关键为,脏我们可以理解为假的,脏数据。也就是两个事务A和事务B。事务B在事务中修改了数据,此时事务A是能够读到事务B中修改的数据,而此时如果事务B中发生了异常进行了回滚,则事务A中读到的数据实际是脏数据,这就是所谓的脏读。根据以上的描述我们也不难理解为什么RU(未提交读)无法避免脏读的问题。
  • 不可重复读:
    不可重读读指的是事务A和事务B,比如在事务A中进行id = 1的数据的读取,而事务B之后对id = 2的数据进行了update并提交。此时在事务A中如果此时在进行id = 1的读取的话,会发现读取的内容与上次的内容不同。这就是所谓的不可重复读,对应的是隔离级别的RC。
  • 幻读
    幻读的概念需要好好理解下。实际想要理解幻读首先需要理解MySQL中的两种读的区别:
    快照读(snapshot read)当前读(current read)。产生这两种读的区别主要是由于MySQL所采用的MVCC的版本控制来进行乐观锁机制。
    我们下面摘取Innodb中的事务隔离级别和锁的关系来进行下解释:

可能有读者会疑惑,事务的隔离级别其实都是对于读数据的定义,但到了这里,就被拆成了读和写两个模块来讲解。这主要是因为MySQL中的读,和事务隔离级别中的读,是不一样的。
我们且看,在RR级别中,通过MVCC机制,虽然让数据变得可重复读,但我们读到的数据可能是历史数据,是不及时的数据,不是数据库当前的数据!这在一些对于数据的时效特别敏感的业务中,就很可能出问题。
对于这种读取历史数据的方式,我们叫它快照读 (snapshot read),而读取数据库当前版本数据的方式,叫当前读 (current read)。很显然,在MVCC中:
快照读:就是select

select * from table ....;

当前读:特殊的读操作,插入/更新/删除操作,属于当前读,处理的都是当前的数据,需要加锁。

select * from table where ? lock in share mode;
select * from table where ? for update;
insert;
update ;
delete;
事务A 事务B
BEGIN BEGIN
SELECT * FROM tb_task;
INSERT INTO tb_task(id, name) VALUES(100, '111');
COMMIT;
UPDATE tb_task SET name = '123';
COMMIT

而理解以上的概念对于理解幻读较为重要。实际幻读产生的原因就是由于当前读造成的,所以往往产生幻读发生在Insert, Update, Delete等操作。
比如有两个事务A和B:

事务A 事务B
BEGIN BEGIN
SELECT * FROM tb_task;
INSERT INTO tb_task(id, name) VALUES(100, '111');
COMMIT;
UPDATE tb_task SET name = '123';
COMMIT

也就是在事务A中我们来SELECT时发现只有一条数据,但是我们在进行更新的时候却会更新两条数据(由于事务B插入了一条新的数据并进行了提交)。
在许多时候我们可能会遇到幻读的问题,比如我们在事务A中对于id=1的数据进行更新操作,但是在之前事务B对于id=1的数据进行了删除。则此时事务A将会更新0条数据而不是我们预期的1条数据。

Innodb的默认隔离级别是RR,是通过MVCC和行锁来进行锁定的。
MVCC主要是针对的读,而锁主要是为了写产生冲突。

一段锁或两段锁

事务A 事务B
begin; begin;
update class_teacher set class_name='初三二班' where teacher_id=1; update class_teacher set class_name='初三三班' where teacher_id=1;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
commit;

因为有大量的并发访问,为了预防死锁,一般应用中推荐使用一次封锁法,就是在方法的开始阶段,已经预先知道会用到哪些数据,然后全部锁住,在方法运行之后,再全部解锁。这种方式可以有效的避免循环死锁,但在数据库中却不适用,因为在事务开始阶段,数据库并不知道会用到哪些数据。
数据库遵循的是两段锁协议,将事务分成两个阶段,加锁阶段和解锁阶段(所以叫两段锁)。
我们拿以下的例子进行介绍(出自博客Innodb中的事务隔离级别和锁的关系):

事务A 事务B
begin; begin;
update class_teacher set class_name='初三二班' where teacher_id=1; update class_teacher set class_name='初三三班' where teacher_id=1;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
commit;

为了防止并发过程中的修改冲突,事务A中MySQL给teacher_id=1的数据行加锁,并一直不commit(释放锁),那么事务B也就一直拿不到该行锁,wait直到超时。

这时我们要注意到,teacher_id是有索引的,如果是没有索引的class_name呢?update class_teacher set teacher_id=3 where class_name = '初三一班';
那么MySQL会给整张表的所有数据行的加行锁。这里听起来有点不可思议,但是当sql运行的过程中,MySQL并不知道哪些数据行是 class_name = '初三一班'的(没有索引嘛),如果一个条件无法通过索引快速过滤,存储引擎层面就会将所有记录加锁后返回,再由MySQL Server层进行过滤。

但在实际使用过程当中,MySQL做了一些改进,在MySQL Server过滤条件,发现不满足后,会调用unlock_row方法,把不满足条件的记录释放锁 (违背了二段锁协议的约束)。这样做,保证了最后只会持有满足条件记录上的锁,但是每条记录的加锁操作还是不能省略的。可见即使是MySQL,为了效率也是会违反规范的。(参见《高性能MySQL》中文第三版p181。

实际除了行锁外,为了防止这种事务之间写入造成的冲突产生,还引入了GAP间隙锁。更多的内容可以参见《高性能MySQL》。

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

推荐阅读更多精彩内容