数据库事务会阻止竞争条件吗?

时间:2011-06-25 11:57:28

标签: sql database concurrency transactions race-condition

我不完全清楚数据库系统中的事务是做什么的。我知道它们可以用来完全回滚更新列表(例如在一个帐户上扣钱并将其添加到另一个帐户),但这就是他们所做的一切吗?具体来说,他们可以用来预防竞争条件吗?例如:

// Java/JPA example
em.getTransaction().begin();
User u = em.find(User.class, 123);
u.credits += 10;
em.persist(u); // Note added in 2016: this line is actually not needed
em.getTransaction().commit();

(我知道这可能是作为单个更新查询编写的,但这并非总是如此)

此代码是否受到竞争条件的保护?

我最感兴趣的是MySQL5 + InnoDB,但也欢迎一般的答案。

4 个答案:

答案 0 :(得分:22)

TL / DR:交易固有地阻止所有竞争条件。在所有实际数据库实现中,您仍需要锁定,中止和重试处理或其他保护措施。 Transactions are not a secret sauce you can add to your queries to make them safe from all concurrency effects

分离

您对问题的看法是ACID - isolation中的。学术上纯粹的想法是,事务应该提供完美的隔离,因此结果与每个事务串行执行的结果相同。实际上,在真正的RDBMS实现中很少出现这种情况;功能因实现而异,并且可以通过使用较弱的隔离级别(例如READ COMMITTED)来削弱规则。在实践中,即使在SERIALIZABLE隔离时,您也不能认为交易会阻止所有竞争条件

有些RDBMS比其他RDBMS具有更强大的能力。例如,PostgreSQL 9.2和更新版本具有非常好的SERIALIZABLE隔离,可以检测事务之间的大多数(但不是全部)可能的交互,并且中止除了一个冲突事务之外的所有事务。因此它可以非常安全地并行运行事务。

很少,如果有任何 3 ,系统具有真正完美的SERIALIZABLE隔离,可防止所有可能的种族和异常,包括锁升级和锁定顺序死锁等问题。

即使有强烈的隔离,一些系统(如PostgreSQL)也会中止冲突的事务,而不是让它们等待并连续运行它们。您的应用必须记住它正在做什么并重新尝试交易。因此,虽然事务阻止了与并发相关的异常存储到数据库,但它是以对应用程序不透明的方式完成的。

原子性

可以说数据库事务的主要目的是提供原子提交。在您提交事务之前,更改不会生效。提交时,所有更改都会在与其他事务相关的同一时刻生效。没有任何事务只能看到某些事务所做的更改 1,2 。同样,如果您ROLLBACK,那么任何其他交易都不会看到任何交易的变化;就好像你的交易从未存在过一样。

ACID 中的 A

耐久性

另一个是耐久性 - D D 。它指定当您提交事务时,它必须真正保存到存储器中,以便在断电或突然重启等故障时继续存在。

一致性:

请参阅wikipedia

乐观并发控制

而不是使用锁定和/或高隔离级别,Hibernate,EclipseLink等ORM通常使用optimistic concurrency control(通常称为“乐观锁定”)来克服较弱隔离级别的限制,同时保持性能

这种方法的一个关键特性是它允许您跨多个事务跨越工作,这对于具有高用户数且在与任何给定用户的交互之间可能存在长时间延迟的系统来说是一个很大的优势。

参考

除了文字内链接外,请参阅the PostgreSQL documentation chapter on locking, isolation and concurrency。即使你使用不同的RDBMS,你也会从它解释的概念中学到很多东西。


1 为简单起见,我忽略了很少实现的READ UNCOMMITTED隔离级别;它允许脏读。

2 正如@meriton指出的那样,推论并不一定如此。 Phantom reads出现在SERIALIZABLE以下的任何地方。正在进行的事务的一部分没有看到一些更改(通过尚未提交的事务),然后正在进行的事务的下一部分在另一个事务中看到更改提交。

3 那么,IIRC SQLite2通过在尝试写入时锁定整个数据库来实现,但这并不是我称之为并发问题的理想解决方案。

答案 1 :(得分:14)

数据库层支持不同程度的事务的原子性,称为隔离级别。检查数据库管理系统的文档,了解支持的隔离级别及其权衡。最强的隔离级别 Serializable 要求事务执行,就好像它们是逐个执行一样。这通常通过在数据库中使用独占锁来实现。这可能导致死锁,数据库管理系统通过回滚一些涉及的事务来检测和修复死锁。这种方法通常被称为悲观锁定

许多对象关系映射器(包括JPA提供程序)也支持乐观锁定,其中数据库中不会阻止更新冲突,但会在应用程序层中检测到更新冲突,然后应用程序层回滚事务。如果启用了乐观锁定,典型的示例代码执行将发出以下sql查询:

select id, version, credits from user where id = 123;  

让我们说这会返回(123,13,100)。

update user set version = 14, credit = 110 where id = 123 and version = 13;

数据库告诉我们更新了多少行。如果是一个,则没有冲突的更新。如果为零,则发生冲突更新,JPA提供程序将执行

rollback;

并抛出异常,以便应用程序代码可以处理失败的事务,例如通过重试。

总结:无论采用哪种方法,您的陈述都可以在竞争条件下保持安全。

答案 2 :(得分:4)

它取决于隔离级别(在序列化中它将防止竞争条件,因为通常在可序列化的隔离级别事务中按顺序处理,而不是在并行处理(或者至少使用独占锁定,因此修改相同行的事务,按顺序执行)。 为了防止竞争条件,最好手动锁定记录(mysql例如支持'select ... for update'语句,它在所选记录上获取写锁定)

答案 3 :(得分:1)

这取决于具体的rdbms。通常,事务获取在查询评估计划期间确定的锁。有些可以请求表级锁,其他列级别,其他记录级别,第二级是性能优先。对你的问题的简短回答是肯定的。

换句话说,事务意味着对一组查询进行分组并将它们表示为原子操作。如果操作失败,则回滚更改。我不确切知道你使用的适配器是做什么的,但是如果它符合交易的定义你应该没问题。

虽然这可以保证防止竞争条件,但它并没有明确地防止饥饿或死锁。事务锁定管理器负责这一点。表锁有时会被使用,但它们会降低并发操作的数量。