TL/DR: 事务并不会本质上防止所有的竞态条件。在所有实际的数据库实现中,你仍然需要锁定、中止和重试处理或其他保护措施。
事务并不是一个秘密酱汁,你可以添加到查询中,使它们免受所有并发效应的影响。
隔离性
你的问题涉及到ACID中的I——隔离性。学术上的纯理念是,事务应该提供完美的隔离性,以便结果与每个事务串行执行时相同。实际上,在真实的RDBMS实现中很少有这种情况;能力因实现而异,规则可以通过使用较弱的隔离级别(如READ COMMITTED
)而被削弱。在实践中,即使在SERIALIZABLE
隔离级别下,你也不能假设事务可以防止所有竞态条件。
一些关系型数据库比其他数据库具有更强的能力。例如,PostgreSQL 9.2及更新版本具有相当不错的SERIALIZABLE隔离级别,可以检测到大部分(但不是全部)可能存在的事务交互,并且终止与之冲突的所有事务,从而可以安全地并行运行事务。
很少有系统(如果有的话)具有真正完美的SERIALIZABLE隔离级别,可以防止所有可能的竞争和异常情况,包括锁升级和锁顺序死锁等问题。
即使使用强隔离级别,一些系统(如PostgreSQL)仍会终止有冲突的事务,而不是让它们等待并按顺序运行。因此,您的应用程序必须记住正在进行的操作并重新尝试事务。因此,虽然事务已经防止了并发相关的异常存储到数据库中,但是它所采用的方式对于应用程序来说不是透明的。
原子性
数据库事务的主要目的之一是提供原子提交。在提交事务之前,更改不会生效。当您提交时,就其他事务而言,所有更改都同时生效。任何事务都无法仅看到事务所做的某些更改。同样地,如果您回滚,则任何其他事务都不会看到事务的更改;就好像您的事务从未存在过。
这就是ACID中的A。
耐久性
另一个是耐久性-ACID中的D。它指定了当您提交事务时,必须真正保存到能够经受得住故障(如断电或突然重新启动)的存储器中。
一致性:
请参见wikipedia
乐观并发控制
与使用锁定和/或高隔离级别不同,像Hibernate、EclipseLink等ORM通常使用乐观并发控制(通常称为“乐观锁定”)来克服较弱隔离级别的限制,同时保持性能。
这种方法的一个关键特点是它可以让您跨越多个事务进行工作,在具有高用户数并且可能存在长时间延迟的系统中,这是一个非常大的优势。
参考资料
除了文本链接外,还可以查看PostgreSQL文档中关于锁定、隔离和并发的章节。即使您使用不同的RDBMS,也可以从其解释的概念中学到很多东西。
1为了简单起见,我忽略了很少被实现的READ UNCOMMITTED
隔离级别;它允许脏读。
2正如@meriton所指出的那样,反之并不一定成立。幻读会在SERIALIZABLE
以下的任何级别中发生。正在进行的事务的某个部分看不到某些更改(由尚未提交的事务),然后正在进行的事务的下一个部分在另一个事务提交时确实看到了这些更改。
3好吧,如果我没记错的话,SQLite2通过锁定整个数据库来解决写入问题,但这并不是我所说的并发问题的理想解决方案。
SERIALIZABLE
支持的数据库中使用SERIALIZABLE
隔离级别,则不安全。请参阅我最近撰写的关于此主题的文章:http://blog.2ndquadrant.com/postgresql-anti-patterns-read-modify-write-cycles/ - Craig Ringer