数据库并发控制——基于事务锁的并发控制

写在开头:在最开始写这篇笔记的时候我是不知道MVCC(多版本并发控制)的,所以我一边写,一边充满了困惑。比如这个RR的实现,在读的时候加S锁直到事务提交,但是我尝试了很多遍发现我无论使用Oracle还是Mysql,读数据都不会阻塞写数据,这一度让我很困惑。不是说了加S锁,就没有办法加X锁所以其他事务没办法修改数据,从而实现了可重复读嘛。。。。直到我认识到这是实现数据库并发控制的不同的两种不同方式,我才恍然大悟。那么先介绍基于事务锁的并发控制

隔离性分为四个级别:

  • 读未提交:(Read Uncommitted)
  • 读已提交(Read Committed) 大多数数据库默认的隔离级别
  • 可重复读(Repeatable-Read)
  • 序列化(serializable)

隔离是有代价的,对于基于事务锁的并发控制而言,隔离度越高,并行度就越低。但事务的一致性会越高。通过选择合适的数据一致性从而达到尽可能高的并行度,最大程度发挥系统的性能。


读未提交

数据库支持并发读写的话,是有很多问题要考虑的,如果我们不进行任何并发控制,会出现下面的情况。

比如一个收入表,本来有100块钱,小红买了一个商品,进账10块钱。小李,也买了个商品,也进账30块钱。此时卖出了两件商品,收入表应该增加到140块钱。但是由于此时并发没有任何限制。可能出现不准确的情况。

小红 小李
读取收入表A:100 -
- 读取收入表A:100
A=A+10 -
写回收入表A:110 -
- A=A+30
- 写回收入表A:130

此时小红的增加记录,就可能会被小李覆盖掉。
那怎么办呢。对于任何一种数据库,这种丢失修改低级的错误是绝不允许出现的。

怎么办呢。

写数据的时候,加上排他锁(X锁),读数据不加锁。
在写某行数据的时候,其他进程对该行数据不能有写操作

通过加上X锁,流程就变成了这个样子:

小红 小李
获取A的X锁:==成功== ---
读取收入表A:100 -
- 获取A的X锁:==失败==(锁等待)
A=A+10 等待中..
写回收入表A:110 等待中..
结束事务,释放A的X锁 等待中..
- 获取A的X锁:==成功==
- A=A+30
- 写回收入表A:150
- 释放A的X锁

这就解决了这种低级错误问题
也是数据库的最低隔离级别:读未提交:(Read Uncommitted)
mysql中支持Read Uncommitted,但是Oracle无法设置该隔离级别。

Read Uncommitted故名思意,就是可以读取到未提交的数据,也就是脏数据。
如何会出现读取到脏数据的情况呢?如下所示:

小红 小李
获取A的X锁:==成功== -
读取收入表A:100 -
A=A+10 -
写回收入表A:110 -
- 读取A的数据:110
RollBack,A恢复为100 -
- 小李读取到了错误的数据...

这里介绍一下从sqlserver官网引用的一段话:

Transactions running at the READ UNCOMMITTED level do not issue shared locks to prevent other transactions from modifying data read by the current transaction. READ UNCOMMITTED transactions are also not blocked by exclusive locks that would prevent the current transaction from reading rows that have been modified but not committed by other transactions. When this option is set, it is possible to read uncommitted modifications, which are called dirty reads. Values in the data can be changed and rows can appear or disappear in the data set before the end of the transaction.

翻译过来就是:
Read Uncommitted事务隔离级别没有使用共享锁去阻止其他事务修改当前事务读取的数据。READ UNCOMMITTED事务也不会使得独占锁阻塞当前事务读取已被修改但未被其他事务提交的行。设置此隔离级别,可以读取未提交的修改,称为脏读——在事务结束前,数据中的值可能被改变,行也可能出现或者消失。

说的很清楚,在READ UNCOMMITTED级别下,A事务进行update,B事务是无法对同一行进行update的,但是读,是没问题的,而A可能commit,也可能rollback,所以这列B可能读到了脏数据。

所以READ UNCOMMITTED写加了X锁,在事务结束之后释放,读不加锁。

不过话说回来,脏读这样的低一致性基本上没有任何系统支持把。。。所以一般是没人设置此隔离级别的。


读已提交

Read Committed可以解决脏读问题,他是如何做到的呢。我们上面已经看到了,脏读之所以发生是因为读操作没有加锁,所以在事务A写数据的过程中,事务B是可以进行读取的。所以呢,我们可以给读操作也加一个锁,叫做共享锁(S锁)。如果某行数据加了X锁,就没法加S锁;加了S锁,就没法加X锁。但是加了S锁,另一个事务还是可以加S锁的。这样呢,读数据的并发行还是不受影响影响的。

此时的流程变成了来这个样子:

小红 小李
获取A的X锁:==成功== -
读取收入表A:100 -
A=A+10 -
写回收入表A:110 -
- 获取A的S锁:==失败==
RollBack,A恢复为100 -
提交事务,释放A的S锁 -
- 获取A的S享锁:==成功==
- 读取A的值:100
- 释放A的S锁

通过S锁,我们只能读取到已提交的数据,解决了脏读问题。

这里需要注意的是小李获取到的S锁是在读取操作结束后立刻释放的,而不是在事务提交时刻才释放,因此,等待在该对象上进行写事务就可以较早获得锁继续执行(而不是在小李事务提交之后才能获得X锁)。这样,数据库系统整体得到了较高的并行度。

不过呢,这种读取操作结束之后立刻释放S锁的做法,可能会导致另一个问题。什么问题呢?往下看(小红的处理是在一个事务中):

小红 小李
获取A的S锁:==成功== -
读取A:100 -
释放A的S锁 -
计算C=A+50=150 -
- 获取A的X锁:==成功==
- 读取A的值:100
- A=A+30
- 写回A的值:130
- 释放A的X锁
获取A的S锁:==成功== -
读取A:130 -
释放A的S锁 -
计算C=A+50=180 -

这个时候小红有点懵逼...哎,同一个事务里面,我第一此计算C的值是150,第二次计算C的值是180.到底怎么搞呢,这数据出现了不一致啊。


可重复读

可重复读,顾名思义,在一个事务中,多次读取某一行数据的值,数据是一致的,不会出现第一次读和第二次读数据不一样的情况。那么他是如何做到的呢?

前面已经提及过了,RC隔离级别是读取操作结束后立刻释放S锁。这样换来了更高的并发性,但是也正是因为这种特性,导致了不可重复度。要实现可重复度也很简单,在事务提交时才释放S锁。如下所示:

小红 小李
事务开始 -
获取A的S锁:==成功== -
读取A:100 -
计算C=A+50=150 -
做其他事情... -
- 获取A的X锁:==失败==
做其他事情... -
再次读取A:100 -
计算C=A+50=100 -
提交事务 -
释放A的S锁 -
- 获取A的X锁:==成功==
- 读取A的值:100
- A=A+30
- 写回A的值:130
- 释放A的X锁

这样呢,小红在一个事务中多次读取并计算C的值,得到的总是相同的结果,实现了可重复度。事务就获得了更高的一致性。但是由于S锁也要等到事务结束才释放,数据库系统整体的并发度进一步降低了。

看似在RR隔离级别下,已经解决了所有的问题,但是啊。锁只能加在已经存在的行上面,不能加在还未出现的行上面对吧。
比如做范围查找:

select * from student where age between 18 and 22;
小红 小李
select * from student where age = 18; -
读取到5行记录 -
获取上述5行的X锁:==成功== -
更新上述5行记录 score = A -
- 插入一行age=18,score=B的记录
select * from student where age = 18; -
多了一行未修改的记录... -

小红第一次查,发现有5行数据,代表有5个学生满足在18至22岁之间。此时呢,小李往student表中插入了一行学生数据,该学生为20岁,然后提交。小红再次查,发现有6行数据了。这下小红懵逼了。感觉出现了幻觉。。这就是所谓的幻读。


序列化

小红 小李
开启事务 -
A操作 -
B操作 -
C操作 增删改查被阻塞
D操作 阻塞中
事务提交 阻塞中
- 操作成功

如何解决幻读问题呢?那就只能序列化串行执行了。。实现了最高的数据一致性,但也导致了最低的并发度。。。

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

推荐阅读更多精彩内容