数据库事务的ACID特性与事务隔离级别小结

前言

今天是本博客开博一周年,一年来写了将近30万字,收获颇丰。但是回头翻看,发现之前写的“漂在半空中”的东西居多。所以最近几天打算搞些平时不会经常注意到,但是又非常基础、重要的知识点,权当复习和查漏补缺,以及训练一下自己“说人话”的能力。

本文只总结概念和理论层面的东西,数据库事务的具体实现细节会以MySQL为例另写文章来说(flag立好了_(:з」∠)_

数据库事务与ACID特性

在数据库系统中,一个事务(transaction)是指由一系列数据库操作组成的一个完整的逻辑过程。考虑最经典的“银行转账”情景,从某个客户的储蓄账户转账1000元到理财账户,可以对应以下的事务描述:

start transaction;
select balance from saving_account where customer_id = 3897;
update saving_account set balance = balance - 1000.00 where customer_id = 3897;
update finance_account set balance = balance + 1000.00 where customer_id = 3897;
commit;

早在1983年的论文《Principles of transaction-oriented database recovery》中,就提出了数据库事务必须具备的四大特性是:原子性(atomicity)、一致性(consistency)、隔离性(isolation)、持久性(durability),合称为ACID。要保证事务的执行是正确而可靠的,就必须满足ACID。下面我们不按A→C→I→D的顺序来,而按更加易于理解的A→D→I→C顺序进行解说。

原子性

一个事务必须被视为一个内部不可分的工作单元,以确保该事务绝对不会被部分执行——即要么完全执行,要么根本不执行。当事务中有操作失败时,所有操作都将回滚,保证数据库回到事务前的状态。原子性可以说是事务最基本的保障,如果没有原子性,假设上文的SQL语句第4行失败,但事务不回滚,仍然提交的话,客户的储蓄账户就会白白损失1000元。

MySQL InnoDB存储引擎使用undo log(回滚日志)机制实现原子性。undo log是逻辑日志,记录了各个行在已提交事务修改前的数据,当事务失败或主动执行rollback语句时,根据undo log的数据恢复事务前的现场。undo log以及下一节提到的redo log统称为MySQL的事务日志,关于它们两个之后再细说。

持久性

一旦事务提交,事务所做的数据改变将是永久的。这意味着数据改变已经被记录,即使系统崩溃,数据也不会因此而丢失(客户的钱都还在)。直白地说,我们总是通过非易失性存储设备(HDD、SSD)来存储数据,只要能够排除存储介质本身的问题,就可以尽量保证物理上的持久性。

但是在实际的DBMS设计中,持久性还要多注意一点,就是缓存的持久化。为了提升性能,数据的更改往往不是实时落盘的,而是先写缓存(如MySQL的buffer pool)再后台同步。如果同步未完成而服务器宕机,数据就丢失了。InnoDB使用redo log(重做日志)机制进一步保障持久性。redo log是物理日志,记录了各个页在已提交事务修改后的数据,系统重启时可根据redo log将那些没来得及落盘的数据写入。

隔离性

某个事务的结果只有在完成之后才能对其他事务可见,也即一个事务不应该影响其它事务运行效果。由于事务是可以并发执行的,如果这些事务查询或修改的是相关联的表和数据,就有可能会相互影响,导致不正确的结果。所以每个事务都有各自的完整的数据空间,本事务所做的修改与其他事务所做的修改要隔离。在查看特定数据的更新时,要么看到另一事务修改之前的状态,要么看到修改之后的状态,不会看到中间状态。

仍然以上文转账事务为例,当DBMS执行完第3条SQL,还未执行第4条时,如果又有一个账户统计事务开始运行,那么它会认为转账还未进行,即那1000元还在储蓄账户里。如果不隔离的话,统计事务在一瞬间就会看到中间状态,即1000元凭空消失了。

上面的原子性和持久性都是针对单个事务的,而隔离性是针对多个事务的,所以更复杂些。在DBMS中实现隔离性实际上就是实现并发控制(concurrency control)机制,MySQL InnoDB使用了锁和MVCC(多版本并发控制)来保证隔离性,这两点之后再说。

容易想到,如果一刀切地保证事务完全隔离,那么DBMS在同一时间就只能执行一个事务,效率大大降低。所以在实际操作中会将隔离性划分级别,本文稍后会总结。

一致性

数据库总是从一种一致性状态转换到另一种一致性状态,也即事务开始前和结束后,数据库的完整性约束没有被破坏。这包含两个层面。

  • 数据库层面:事务执行前后,数据都符合预先设置的约束,如唯一约束、外键约束、enum/check约束、数据类型/长度检验、触发器等。
  • 业务层面:事务执行前后,数据都在业务意义上是正确的。例如开头的转账过程,由于是同一客户内部账户互转,所以客户的资产总额是不能变的。

之所以把一致性放在最后,是因为原子性、持久性和隔离性的设计最终都是为了保证一致性而存在的。

小问题:ACID特性中的一致性和CAP理论中的一致性有关联吗?答案是没有,它俩并不是同一个概念。前者是针对(单机)数据库事务的,是内部一致性。后者是针对分布式系统的,是外部一致性。以CAP/BASE理论为代表的分布式基础对我们大数据工作者来说似乎更重要,最近会总结出来。

事务的隔离级别与读现象

在ANSI/ISO SQL 92标准中,定义了4种事务隔离级别,从低到高分别是:未提交读(READ UNCOMMITTED)、提交读(READ COMMITTED)、可重复读(REPEATABLE READ)、可串行化(SERIALIZABLE)。下面分别解释它们,并顺便引出各个隔离级别可能会出现的其他3种读现象,即某事务读取其他事务可能修改的数据的现象。

未提交读

最低的隔离级别,所有事务都可以看到其他未提交事务的执行结果。为了简单,考虑纯粹用锁实现隔离性的DBMS,未提交读级别的事务在读数据时不会加锁,在写数据时只会加行级共享锁。

该级别只比完全不做隔离好一点点,几乎不用在实际应用中。而读取到未提交的数据,就称为脏读(dirty read)现象。为了解释它,假设有以下原始数据表。

然后两个事务并发执行如下图所示的逻辑。

可见,事务2将id = 1的行的age列修改为21,但没有提交,造成事务1查询到的id = 1的行的age也是21,造成了脏读。事务2回滚后,事务1查询到的数据就是错的了。

小问题:脏读现象虽然多数情况下不符合预期,但还是允许存在的,那么脏写(dirty write)允许存在吗?答案是不行的,如果一个事务的修改在未提交时就被另一个事务的修改覆盖,原子性就被打破了。

提交读

这是大多数DBMS(除MySQL外)的默认事务隔离级别。该级别其实就是符合隔离性基本定义的实现:一个事务在开始时只能看到其他已提交的事务所做的改变;一个事务从开始到提交前,所做的数据更改都对其他事务不可见。仍然以锁机制实现隔离性为例,该级别的事务在读数据时会加行级共享锁,读完立即释放;写数据时会加行级排他锁,直到事务结束释放。

在该级别下,脏读现象不会出现,但是需要注意不可重复读(non-repeatable read)现象。如下图示例。

事务1第一次select和第二次select读到的id = 1行的age列的值是不同的(因为事务2中途提交了)。这种同一事务中,对同一行数据获取多次,返回不同结果的现象就是不可重复读。

可重复读

这是MySQL的默认事务隔离级别。既然提交读级别会造成不可重复读的问题,那么在提交读的基础上解决该问题的隔离级别自然就叫可重复读了。仍然以锁机制实现隔离性为例,该级别的事务在读数据时会加行级共享锁,写数据时会加行级排他锁,两个锁都是直到事务结束才释放。这样,在事务1读某行数据到事务1结束的整个过程中,事务2肯定无法修改该行数据,得到的结果总是一样的,problem solved。

但是,该级别仍然无法解决最高级的一种读现象,即幻读(phantom read)。如下图示例。

事务1执行了一个范围查询,第一次执行时返回2条记录。事务2向表插入了一条新记录并直接提交,导致事务1重新执行该范围查询时,查到了事务2插入的那条记录,并返回3条记录。可见,幻读实际上是不可重复读在范围查询时的一种特殊情况。

可串行化

最高的隔离级别。该级别的事务在读数据时会加行级共享锁,对范围查询则会特别加上范围锁,写数据时会加行级排他锁,并且都是直到事务结束才释放,这样就连幻读都不会出现了。如果要保证绝对安全,即事务都是严格顺序执行的,还可以将行级锁改为表级锁。但同样地,这个级别的并发性是最低的,只有在强制要求数据稳定性时,才会选用它。

The End

强烈推荐《高性能MySQL》(High Performance MySQL)一书,嗯嗯。

民那晚安。祝身体健康,百毒不侵。希望疫情快点过去。

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

推荐阅读更多精彩内容