谈数据库事务隔离性

写在前面

近两年分布式数据库技术加速发展,而由于金融行业技术生态的限制,周围很多同学对其并没有深入的了解,所以进行高性能、高可靠系统设计时往往缺少这一利器。Ivan希望以系列文章的方式与大家交流探讨,加深我们对分布式数据库的认识。本文是该系列文章的第一篇,主要探讨事务管理中的隔离性,厘清相关概念和关键技术,为后面阐述分布式数据库的事务管理做一个铺垫,姑且算是一篇前传吧。


正文

我们首先从定义出发,事务管理包括原子性、一致性、隔离性和持久性四个方面,即ACID。所有数据库专著都会给出这个四个特性的定义,本文我们引用了Jim Gray对其的定义。Jim Gray是事务处理方面的大神,本文中很多内容引自他的论文和专著。为避免转译过程引入的歧义,这里我们直接使用原文。

Jim Gray是事务处理方面的大师,本文中很多内容都来自他的专著和论文。为避免翻译引入的歧义,这里我们直接引用原文。

Atomicity: Either all the changes from the transaction occur (writes, and messages sent), or none occur.

Consistency: The transaction preserves the integrity of stored information.

Isolation: Concurrently executing transactions see the stored information as if they were running serially (one after another).

Durability: Once a transaction commits, the changes it made (writes and messages sent) survive any system failures.

在上述隔离性(Isolation)的定义中,我们可以发现其目标是使并发事务的执行效果与串行一致,但在具体技术实

在上述隔离性(Isolation)的定义中,我们可以发现其目标是使并发事务的执行效果与串行一致,但在具体技术实现上往往需要在并发能力和串行化效果之间进行平衡,很难两者兼顾。平衡的结果就是会出现违反串行效果的现象即异常现象(Phenomenon)。通常来说,隔离级别的提升伴随着并发能力的下降,两者负相关。各种数据库在谈到隔离级别时都会引用ANSI SQL-92标准隔离级别,我们来看看它的具体内容。

ANSI SQL-92 Isolation Levels

ANSI SQL-92可能是最早提出了基于异常现象来定义隔离级别的方法,同时没有将隔离级别与具体实现机制绑定,隔离的实现可以基于锁(lock-based)或者无锁(lock-free),兼容了后续的技术发展。该标准根据三种异常现象将隔离性定义为四个级别,具体如下。

脏读,事务(T1)中修改的数据项在尚未提交的情况下被其他事务(T2)读取到,而T1进行Rollback操作,则T2刚刚读取到的数据并没有实际存在。

不可重复读,T1读取数据项,T2对其中的数据进行了修改或删除且Commit成功。如果T1尝试再次读取这些数据,会得到T2修改后的数据或者发现数据已删除。这样T1在一个事务中两次同样条件的读取,且结果集内容变更或结果集数量减少。

幻读,T1使用特定的查询条件获得一个结果集,T2插入新的数据且这些数据符合T2刚刚操作的查询条件。T2 commit 成功后,T1再次执行同样的查询,此时得到的结果集增大。

很多文章都结合数据库产品对上述异常现象的实例和处理机制进行了说明,本文中不再赘述,有兴趣的同学可以参考文末的链接[1]。

ANSI SQL-92标准早在92年发布,但无论是当时还是后来都没有被各大数据库厂商严格遵循,部分原因可能是标准过于简化与实际应用有一定程度的脱离。Jim Gray等人在1995发布了论文“A Critique of ANSI SQL Isolation Levels” (本文中简称为Critique[2])对隔离级别进行更全面的阐述,可以帮助我们加深理解。

Critique Isolation Levels

Critique提出了ANSI SQL-92存在的两个问题,首先是自然语言方式界定的异常现象并不严格导致一些同质化的异常现象被遗漏;其次是一些典型的异常现象并没有被涵盖进去,导致隔离级别存在明显缺失。因此,文中对ANSI SQL-92的三种异常现象(将其编号为A1/A2/A3)进行了扩展(编号为P1/P2/P3),并增加了另外5种常见的异常现象。受限于篇幅,这里仅对两种异常现象进行说明。

Lost Update

丢失更新(Lost Update)是一个经典的数据库问题,由于太过重要所有主流数据库都解决了该问题,我们这里将操作稍加变形来举例。

我们使用MySQL进行演示,创建表并初始化数据

create table account (balance int,name varchar(20)) ENGINE=InnoDB;
insert into account values(50,‘Tom‘);
T1 T2
begin; begin;
select balance into @bal from account where name=‘Tom‘
--------------------
@bal = 50
select balance into @bal from account where name=‘Tom‘
-------------------
@bal = 50
update account set balance = @bal -40 where name = ‘Tom’;
commit;
update account set balance = @bal - 1 where name = ‘Tom’;
commit;

在上述操作中T1、T2串行执行效果是对余额进行两次扣减,分别为40和1,最终值为9,但并行的最终值为49,T2的修改被丢失。我们可以发现Lost update的实质是T1事务读取数据,而后该数据被T2事务修改并提交,T1基于已经过期的数据进行了再次修改,造成T2的修改被覆盖。

Read Skew

读偏序(Read Skew)是RC级遇到的问题。如果数据项x与y存在一致性约束,T1先对读x,而后T2修改x和y后commit,此时T1再读y。T1得到的x与y不满足原有的一致性约束。

MySQL默认隔离级别为RR,我们需要手工设置为RC并初始化数据

set session transaction isolation level read committed;
insert into account values(70,‘Tom‘);
insert into account values(30,‘Kevin‘);
T1 T2
begin; begin;
select * from account where name=’Tom’;
---------------------
balance name
70 Tom
select * from account where name=’Tom’;
---------------------
balance name
70 Tom
update account set balance = balance - 30 where name=‘Tom‘;
update account set balance = balance + 30 where name=’Kevin’;
commit;
select * from account where name=‘Kevin‘;
---------------------
balance name
60 Kevin
commit;

初始数据Tom与Kevin的账户合计为100,在T1事务内的两次读取得到账户合计为130,显然不符合之前的一致性约束。

补充这些异常现象后,Critique给出了新的矩阵,相比ANSI更加完善也更贴合真实的数据库产品。

主流数据库考虑到串行化效果与并发性能的平衡,一般默认隔离级别都介于RC与RR之间,部分提供了Serializable。特别提醒,无论ASNI SQL-92还是Critique的隔离级别都不能确保直接映射到实际数据库的同名隔离级别。

SI&MVCC

快照隔离(SI,Snapshot Isolation)是讨论隔离性时常见的术语,可以做两种的解读,一是具体的隔离级别,SQL Server、CockroachDB都直接定义了这个隔离级别;二是一种隔离机制用于实现相应的隔离级别,在Oracle、MySQL InnoDB、PostgreSQL等主流数据库中普遍使用。多版本并发控制(MVCC,multiversion concurrency control)是通过记录数据项历史版本的方式提升系统应对多事务访问的并发处理能力,例如避免单值(Single-Valued)存储情况下写操作对读操作的锁排斥。MVCC和锁都是SI的重要实现手段,当然也存在无锁的SI实现。以下是Critique描述的SI运作过程。

事务(记为T1)开始的瞬间会获取一个时间戳Start Timestamp(记为ST),而数据库内的所有数据项的每个历史版本都记录着对应的时间戳Commit Timestamp(记为CT)。T1读取的快照由所有数据项版本中那些CT小于ST且最近的历史版本构成,由于这些数据项内容只是历史版本不会再次被写操作锁定,所以不会发生读写冲突,快照内的读操作永远不会被阻塞。其他事务在ST之后的修改,T1不可见。当T1 commit的瞬间会获得一个CT,并保证大于此刻数据库中已存在的任意时间戳(ST或CT),持久化时会将这个CT将作为数据项的版本时间戳。T1的写操作也体现在T1的快照中,可以被T1内的读操作再次读取。当T1 commit后,修改会对那些持有ST大于T1 CT的事务可见。

如果存在其他事务(T2),其CT在T1的运行间隔【ST,CT】之间,与T1对同样的数据项进行写操作,则T1 abort,T2 commit成功,这个特性被称为First-committer-wins,可以保证不出现Lost update。事实上,部分数据库会将其调整为First-write-wins,将冲突判断提前到write操作时,减少冲突的代价。

这个过程不是某个数据库的具体实现,事实上不同数据库对于SI实现存在很大差别。例如,PostgreSQL会将历史版本和当前版本一起保存通过时间戳区分,而MySQL和Oracle都在回滚段中保存历史版本。MySQL的RC与RR级别均使用了SI,如果当前事务(T1)读操作的数据被其他事务的写操作加锁,T1转向回滚段读取快照数据,避免读操作被阻塞。但是RC的快照定义与以上描述不同,也包括了T1执行过程中其他事务提交的最新版本[6]。

此外,我们还有一个重要发现,时间戳是生成SI的关键要素。在单机系统中,唯一时间戳比较容易实现,而对于分布式系统在跨节点、跨数据中心甚至跨城市部署的情况下如何建立一个唯一时钟就成为一个非常复杂的问题,我们暂留下一个伏笔将在后面的专题文章中进行讨论。

Serializable VS SSI

SI是如此有效,甚至在TPC-C benchmark测试中也没有出现任何异常现象[5],但事实上SI不能保证完整的串行化效果。Critique中指出,SI还无法处理A5B(Write Skew,写偏序),如下图所示。

Write Skew

写偏序(Write Skew)也是一致性约束下的异常现象,即两个并行事务都基于自己读到的数据集去覆盖另一部分数据集,在串行化情况下两个事务无论何种先后顺序,最终将达到一致状态,但SI隔离级别下无法实现。下图的“黑白球”常常被用来说明写偏序问题。

如何实现真正的串行化效果呢?事实上,早期的数据库已经通过严格两阶段锁协议(S2PL,Strict Two-Phase Locking)实现了完全的串行化隔离(Serializable Isolation),即正在进行读操作的数据阻塞对应写操作,写操作阻塞所有操作(包括读操作和写操作)。如阻塞造成循环将构成死锁,则需要进行rollback操作。S2PL的问题显而易见,在竞争激烈场景下,阻塞和死锁会造成数据库吞吐量下降和响应时间的增加,所以这种串行化无法应用于实际生产环境。直到SSI的出现,人们终于找到具有实际价值的串行化隔离方案。

串行化快照隔离(SSI, Serializable Snapshot Isolation,也会被翻译为序列化快照)是基于SI改进达到Serializable级别的隔离性。SSI由Michael James Cahill在他的论文"Serializable Isolation for Snapshot Databases"[3]中提出(该论文获得2008 Sigmod Best Paper Award,文章末尾提供了该论文的2009年完整版[4]相关信息,有兴趣的同学可以深入研究)。SSI保留了SI的很多优点,特别是读不阻塞任何操作,写不会阻塞读。事务依然在快照中运行,但增加了对事务间读写冲突的监控用于识别事务图(transaction graph)中的危险结构。当一组并发事务可能产生异常现象(anomaly),系统将通过回滚其中某些事务进行干预以消除anomaly发生的可能。这个过程虽然会导致某些事务的错误回滚(不会导致anomaly的事务被误杀),但可以确保消除anomaly[3]。

从理论模型看,SSI性能接近SI,远远好于S2PL。2012年,PostgreSQL在9.1版本中实现了SSI[7],可能也是首个支持SSI的商业数据库,验证了SSI的实现效果。CockroachDB也从Cahill的论文获得灵感,实现SSI并将其作为其默认隔离级别。

随着技术的发展,SI/SSI已经成为主流数据库的隔离技术,尤其是后者的出现,无需开发人员在代码通过显式锁来避免异常,从而降低了人为错误的概率。在分布式数据库的相关章节中,我们将进一步对SSI实现机制进行深入探讨。



参考文献

[1]Innodb中的事务隔离级别和锁的关系,ameng,https://tech.meituan.com/innodb-lock.html

[2]H. Berenson, P. Bernstein, J. Gray, J.Melton, E. O’Neil,and P. O’Neil. A critique of ANSI SQL isolation levels. InProceedings of the SIGMOD International Conference on Management of Data, pages1–10, May 1995.

[3]Michael J. Cahill, Uwe R?hm, and Alan D.Fekete. 2008. Serializable isolation for snapshot databases. In SIGMOD ’08:Proceedings of the 2008 ACM SIGMOD international conference on Management of data, pages 729–738, New York, NY, USA. ACM.

[4]Michael James Cahill. 2009. Serializable Isolation for Snapshot Databases. Sydney Digital Theses. University of Sydney, School of Information Technologies

[5] A. Fekete, D. Liarokapis, E. O’Neil, P.O’Neil, andD. Shasha. Making snapshot isolation serializable. In ACM transactions on database systems, volume 39(2), pages 492–528, June 2005.

[6]姜承尧,MySQL技术内幕:InnoDB存储引擎机, 械工业出版社, 2011

[7]https://wiki.postgresql.org/wiki/Serializable

原文地址:https://www.cnblogs.com/ivan-uno/p/8274355.html

时间: 2024-07-31 09:10:22

谈数据库事务隔离性的相关文章

数据库事务隔离级别(转)

1.什么是事务,事务的特性是什么? 在数据库中事务是工作的逻辑单元,一个事务是由一个或多个完成一组的相关行为的SQL语句组成,通过事务机制确保这一组SQL语句所作的操作要么都成功执行,完成整个工作单元操作,要么一个也不执行. SQL92标准定义了数据库事务的四个特点: 原子性(Atomicity):一个事务里面所有包含的SQL语句是一个执行整体,不可分割,要么都做,要么都不做. 一致性(Consistency):事务开始时,数据库中的数据是一致的,事务结束时,数据库的数据也应该是一致的. 隔离性

MySQL数据库事务隔离级别(Transaction Isolation Level)

今天在学习JDBC的时候看到了关于MySql的事务的隔离级别的问题,感觉内容挺高级的,所以记录一篇文章,以备后面使用. 数据库隔离级别有四种,应用<高性能mysql>一书中的说明: 然后说说修改事务隔离级别的方法: 1.全局修改,修改mysql.ini配置文件,在最后加上 1 #可选参数有:READ-UNCOMMITTED, READ-COMMITTED, REPEATABLE-READ, SERIALIZABLE. 2 [mysqld] 3 transaction-isolation = R

数据库事务隔离级别-- 脏读、幻读、不可重复读

一.数据库事务隔离级别 数据库事务的隔离级别有4个,由低到高依次为Read uncommitted .Read committed .Repeatable read .Serializable ,这四个级别可以逐个解决脏读 .不可重复读 .幻读 这几类问题. √: 可能出现    ×: 不会出现   脏读 不可重复读 幻读 Read uncommitted √ √ √ Read committed × √ √ Repeatable read × × √ Serializable × × × 注意

Atitit.数据库事务隔离级别 attilax 总结

Atitit.数据库事务隔离级别 1. 事务隔离级别的作用 1 2. 在的隔离级别 2 3. 常见数据库的默认管理级别 3 1. 事务隔离级别的作用 较低的隔离级别可以增强许多用户同时访问数据的能力,但也增加了用户可能遇到的并发副作用(例如脏读或丢失更新)的数量.相反,较高的隔离级别减少了用户 可能遇到的并发副作用的类型,但需要更多的系统资源,并增加了一个事务阻塞其他事务的可能性.应平衡应用程序的数据完整性要求与每个隔离级别的开销,在此 基础上选择相应的隔离级别.最高隔离级别(可序列化)保证事务

数据库事务隔离和锁

概述 数据库事务隔离和锁的东西,学习了一下,现讲解一下自己的理解 数据库事务隔离 概念 事务:把多条sql语句作为一个整体和数据库交互.这里需要注意,一个事务可有多条SQL语句,虽然这些SQL语句最终的结果变成一个整体,要成功都成功,要失败都失败,但是,里面的每条SQL语句却是单条执行的. 数据库事务隔离:讲的是多个事务(操作同一张表)之间,一个事务内部单条select语句执行的结果在其他事务中的时机. 事务隔离级别 Read Uncommitted:读未提交,事务中的select语句查询表的结

Net Core中数据库事务隔离详解——以Dapper和Mysql为例

Net Core中数据库事务隔离详解--以Dapper和Mysql为例 事务隔离级别 准备工作 Read uncommitted 读未提交 Read committed 读取提交内容 Repeatable read (可重读) Serializable 序列化 总结 事务隔离级别 .NET Core中的IDbConnection接口提供了BeginTransaction方法作为执行事务,BeginTransaction方法提供了两个重载,一个不需要参数BeginTransaction()默认事务

数据库:自己理解的“数据库事务隔离级别”

转载请注明出处: jiq?钦's technical Blog - 季义钦 引言:在网上搜了很多关于事务的文章,感觉单独来看都很难看懂,所以综合自己的理解写一篇我自己能理解的关于关系型数据库事务的文章. 一.事务特征 我们都知道数据库事务具备ACID特性: Atomic(原子性):一个事务要么成功,要么失败 Consistency(一致性):一致性代表了底层数据存储的完整性.事务执行前后数据库都必须处于一个合法的状态.什么才是一个合法的状态? 比如满足数据库的唯一性约束.数据类型验证.引用完整性

浅谈数据库的隔离等级

数据库事务的隔离等级,英语叫做 Transaction Isolation Level. 最近在给客户维护项目的时候,对一个表在两个进程中同时做更新和查询时碰到了死锁(DeadLock),数据表里有几百万上千万条记录,上面的处理当时是更新几千条记录, 查询整张表. 这是前提,为了搞明白这个死锁,大概涉及到死锁,隔离等级,锁升级以及Trace跟踪的内容,陆续整理出来,今天说说隔离等级. 概要 有下面3种读取不具合的现象,首先说说关于RDBMS的ACID特性的I(Isolation-隔离性) ·脏读

浅谈数据库--事务(mysql)

事务   事务其实是一组对数据库增删改操作的组合,可以这样来理解,当你往某个人身上打1000元的时候,在数据库中会发生两个改变,一个是你的钱减少了,另一个是那个人的钱增加了,这两个操作必须同时满足,不然问题就大了,怎样保证两个操作全部执行,这就需要mysql事务的支持. mysql是支持事务的,但首先确认你是InnoDB存储引擎 mysql事务是为了维护数据库的完整性,堆成批量的语句要么全部执行,要么全部不执行.一般用来管理insert delete 和update语句的. 事务的特点: 1.事