译 原文链接:https://vladmihalcea.com/a-beginners-guide-to-acid-and-database-transactions/
@
介绍
事务在当今的企业系统中无处不在,在高度并发的环境中也可以提供数据一致性。因此,让我们首先了解相关的名词以及在什么样的场景使用它。
事务是读/写操作的集合,仅当所有包含的操作都成功时才成功。
事务具有四个特性(通常称为ACID):
- Atomicity(原子性)
- Consistency(一致性)
- Isolation(隔离性)
- Durability(持久性)
在关系数据库中,每个SQL语句必须在事务范围内执行。在不显式定义事务范围的情况下,数据库将使用隐式事务,隐式事务环绕每个单独的语句。隐式事务在语句执行之前开始,在语句执行之后结束(提交或回滚)。隐式事务模式通常称为自动提交。
对于企业应用程序,通常会希望避免使用自动提交模式,因为它会严重影响性能,并且不允许你在单个原子工作单元中包含多个DML操作。
Atomicity(原子性)
原子性把单个操作作为一个整体并将其转变为全部成功或全部失败的单元,只有当所有包含的操作都成功时,该操作才能成功。
事务可能封装状态的更改(除非它是只读)。无论在任何给定时间有多少个并发事务,必须使系统始终保持状态一致。
Consistency(一致性)
一致性意味着对每个已提交的事务强制执行一致性约束。也就是说所有键,数据类型,检查和触发器均成功,并且不会触发任何一致性冲突。
Isolation(隔离性)
事务需要并发控制机制,即使在被交错时也能保证正确性。隔离给我们带来的好处是隔离未提交的事务更改状态,失败的事务永远都不会影响当前事务的状态。通过使用悲观锁或乐观锁机制的并发控制来实现隔离。
Durability(持久性)
成功的事务必须永久更改系统的状态,并且在系统停止之前,将状态更改记录在持久事务日志中。如果突然系统崩溃或断电,那么所有未完成的已提交事务都可能会被重放。
对于JMS这样的消息传递系统,事务不是强制性的。这就是我们拥有无事务确认模式的原因。
文件系统操作通常是无事务管理的,但是如果你的业务需求需要对文件进行事务操作,则可以使用XADisk之类的工具。
尽管消息传递和文件系统可选择性地使用事务,但是对于数据库管理系统,事务是强制性。
挑战
ACID是一个久远的说法。吉姆·格雷(Jim Gray)在我出生之前就已经描述了原子性,一致性和持久性。但是那篇论文没有提到隔离性。如果我们想到70年代后期的数据库管理系统,这是可以理解的,吉姆·格雷说:
“目前,最大的航空公司和银行在任何时刻都有大约10,000个操作和大约100个活跃的事务”。
因此,主要的付出都花在保证交付正确性上,而不是并发上。从那个时候到现在,情况发生了翻天覆地的变化,如今,即使是较低的设置也有1000 TPS。
从数据库的角度来看,原子性是固定属性,但是出于性能/可伸缩性的考虑,其它的特性都需要权衡。
如果数据库系统由多个节点组成,则分布式系统一致性(CAP定理中的C,而不是ACID中的C)要求将所有更改都同步到所有节点(多主从复制)。如果副本节点是异步更新的,那么我们将违反一致性规则,系统将“最终保持一致”。
彼得·贝利斯(Peter Bailis)的一篇很好的文章解释了CAP定理中的一致性和ACID中的一致性之间的区别。
事务是数据状态转换,因此即使所有事务同时执行,系统也必须像所有事务都是以串行形式发生一样进行操作。
如果始终只有一个连接运行,那么串行将不会增加任何并发控制成本。实际上,所有事务系统都必须兼容并发请求,因此序列化会影响可伸缩性。阿姆达尔定律描述了串行执行与并发之间的关系:
“在并行计算中使用多个处理器的程序的速度受到程序顺序部分所需时间的限制。”
稍后你将看到,大多数数据库管理系统都选择(默认情况下)放宽数据正确性的要求,以实现更好的并发性。
如果企业系统业务需求不要求持久性事务,那么对于高性能集群数据库来说,持久性发挥作用才有意义。但是,大多数情况下,持久性最好保持不变。
隔离级别
尽管某些数据库管理系统提供了MVCC,但通常并发控制是通过锁来实现的。但是众所周知,锁会增加执行代码的可序列化部分,从而影响并行效率。
SQL标准定义了四个隔离级别:
- 读取未提交(READ_UNCOMMITTED)
- 读取已提交(READ_COMMITTED)
- 可重复读(REPEATABLE_READ)
- 串行化(Serializable)
除串行化级别外,其他所有级别都可能受到数据异常的影响,不同级别可能发生的数据异常现象如下:
隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
读取未提交 | 允许 | 允许 | 允许 |
读取已提交 | 阻止 | 允许 | 允许 |
可重复读 | 阻止 | 阻止 | 允许 |
串行化 | 阻止 | 阻止 | 阻止 |
异常现象
但是,我们刚刚列出的所有异常现象是什么?我们对每一个进行讨论。
Dirty read(脏读)
当允许事务读取其他正在运行的事务的未提交更改时,就会发生脏读。发生这种情况是因为没有锁阻止它。在上图中,您可以看到第二个事务使用了不一致的值,因为第一个事务已回滚。
有关“脏读”异常的更多信息,请查看这篇文章。
Non-repeatable read(不可重复读)
所谓不可重复读,由于并发事务刚刚更新了我们正在读取的记录,因此连续读取产生不同的结果。这是我们不想要的,因为最终使用了过时的数据。通过在当前事务的整个持续时间内在读取记录上保留一个共享锁(读锁),可以防止这种情况。
有关不可重复读取异常的更多信息,请查看这篇文章。
Phantom read(幻读)
当后续的事务插入了数据,刚好插入的数据又能被并行的事务先前的查询查到,就会发生幻读。因此,我们最终将使用过时的数据,这可能会影响我们的业务运行。使用范围锁或谓词锁可以防止这种情况。
有关幻读异常的更多信息,请查看这篇文章。
其它异常现象
即使在SQL标准中未提及,你也应注意其它的现象,例如:
- 丢失更新
- 读取偏差
- 写入偏差
知道何时会发生这些现象就可以正确地解决它们,这就是数据完整性的全部意义所在。
默认隔离级别
即使SQL标准要求使用SERIALIZABLE隔离级别,但大多数数据库管理系统使用不同的默认级别。
数据库 | 隔离级别 |
---|---|
Oracle | 读取已提交 |
MySQL | 可重复读 |
Microsoft SQL Server | 读取已提交 |
PostgreSQL | 读取已提交 |
DB2 | CURSOR STABILITY |
总结
通常,READ COMMITTED是合适的选择,因为即使SERIALIZABLE都不能保护你免受丢失更新的影响,在更新丢失的情况下,读/写发生在不同的事务(和Web请求)中。你应该考虑你的系统要求,并进行测试以确定哪个隔离级别最适合你的需求。
博主水平有限,难免错漏,欢迎指出,或直接查看原文!