出于与您在问题中指出的相同原因,我已开始研究同一主题。我对 SO 中给出的答案感到困惑,因为它们是部分答案并且没有提供全局。在我阅读了来自不同 RDMS 提供商的几个文档页面后,这些是我的看法:
交易
语句是数据库命令,主要用于读取和修改数据库中的数据。事务是单个或多个语句执行的范围。它们提供了两件事:
- 一种保证事务中的所有语句都正确执行的机制,或者在出现单个错误的情况下,由这些语句修改的任何数据都将恢复到其最后的正确状态(即回滚)。这种机制提供的功能称为原子性。
- 一种机制,可保证并发读取语句可以查看数据,而不会发生下文所述的部分或全部现象。
脏读:事务读取并发写入的数据
未提交的事务。
不可重复读取:事务重新读取它之前读取的数据
并发现数据已被另一个事务修改(即
自首次读取后提交)。
幻读:事务重新执行查询,返回一组
满足搜索条件并找到行集的行
由于另一个最近提交的条件,满足条件已更改
交易。
序列化异常:成功提交组的结果
事务与所有可能的运行顺序不一致
这些交易一次一项。
这种机制提供的称为隔离,让语句选择事务中不应发生哪些现象的机制称为隔离级别。
例如,这是 PostgreSQL 的隔离级别/现象表:
如果任何描述的承诺被数据库系统破坏,更改将回滚并通知调用者。
如何实施这些机制以提供这些保证如下所述。
锁定类型
-
独占锁:当在资源上获得独占锁时,无法在该资源上获得其他独占锁。排他锁总是在修改语句(INSERT、UPDATE 或 DELETE)之前获取,并在事务完成后释放。要在修改语句之前显式获取独占锁,您可以使用 FOR UPDATE(PostgreSQL, MySQL) 或 UPDLOCK (T-SQL) 等提示。
-
共享锁:可以在一个资源上获取多个共享锁。但是,共享锁和排他锁不能同时获取一个资源。根据隔离级别的数据库实现,可能会或可能不会在读取语句(SELECT、JOIN)之前获取共享锁。
锁定资源范围
-
行:执行语句的单行。
-
范围:基于语句中给定条件的特定范围(SELECT ... WHERE)。
-
表格:整个表格。 (主要用于防止批量更新等大语句出现死锁。)
以 SQL-Server 不同隔离级别的默认共享锁行为为例:
死锁
锁定机制的缺点之一是死锁。当一条语句进入等待状态时会发生死锁,因为请求的资源被另一个等待语句持有,而另一个等待语句又在等待另一个等待语句持有的另一个资源。在这种情况下,数据库系统检测到死锁并终止其中一个事务。不小心使用锁会增加死锁的机会,但即使没有人为错误也可能发生。
快照(数据版本)
这是一种隔离机制,可为语句提供在特定时间获取的数据的副本。
-
语句开始:为语句执行开始时获取的语句提供数据副本。通过保留这些数据直到事务完成,它还有助于回滚机制。
-
事务开始:为事务开始时的语句提供数据副本。
所有这些机制共同提供一致性。
当谈到乐观锁和悲观锁时,它们只是对并发问题方法分类的命名。
悲观并发控制:
锁定系统可防止用户以以下方式修改数据
影响其他用户。在用户执行导致
锁定要应用,其他用户无法执行将
与锁发生冲突,直到所有者释放它。这就是所谓的
悲观控制,因为它主要用于环境
数据竞争激烈,保护数据的成本
带锁的成本小于回滚事务的成本,如果
发生并发冲突。
乐观并发控制:
在乐观并发控制中,用户在操作时不会锁定数据
阅读。当用户更新数据时,系统会检查是否有另一个
用户在读取数据后更改了数据。如果另一个用户更新了
数据,引发错误。通常,收到错误的用户
回滚事务并重新开始。这叫乐观
因为它主要用在环境低的地方
数据争用,以及偶尔回滚的成本
事务低于读取时锁定数据的成本。
例如,默认情况下 PostgreSQL 使用快照来确保读取的数据没有更改,如果更改则回滚,这是一种乐观的方法。但是,SQL-Server 默认使用读锁来提供这些承诺。
实施细节可能会根据您选择的数据库系统而有所不同。但是,根据数据库标准,他们需要使用这些机制以一种或另一种方式提供那些声明的事务保证。如果您想了解有关该主题或特定实施细节的更多信息,下面是一些对您有用的链接。
- SQL-Server - Transaction Locking and Row Versioning Guide
- PostgreSQL - Transaction Isolation
- PostgreSQL - Explicit Locking
- MySQL - Consistent Nonlocking Reads
- MySQL - Locking
- Understanding Isolation Levels (Video)