0

让我们假设在 SQL 窗口 1 中我这样做:

-- query 1 
BEGIN TRANSACTION;
UPDATE post SET title = 'edited' WHERE id = 1;
-- note that there is no explicit commit

然后从另一个窗口(窗口 2)我做:

-- query 2
SELECT * FROM post WHERE id = 1;

我得到:

1 | original title

这很好,因为默认隔离级别是 READ COMMITTED 并且因为查询 1 从未提交,所以在我从窗口 1 显式提交之前,它执行的更改是不可读的。

事实上,如果我在窗口 1 中执行以下操作:

COMMIT TRANSACTION;

如果我重新运行查询 2,我可以看到更改。

1 | edited

我的问题是:

为什么查询 2 在我第一次运行时返回正常?我期望它会阻塞,因为窗口 1 中的事务尚未提交,并且放置在行上的锁id = 1是(应该是)一个未释放的独占锁,它应该像在窗口 2 中执行的那样阻塞读取。其余的都是有道理的对我来说,但我希望在SELECT执行窗口 1 中的显式提交之前会卡住。

4

2 回答 2

3

您描述的行为在任何事务性关系数据库中都是正常和预期的。

如果 PostgreSQL 向您显示第一个值editedSELECT那么这样做是错误的——这被称为“脏读”,在数据库中是个坏消息。

PostgreSQL 将被允许等待SELECT直到你提交或回滚,但 SQL 标准并没有要求它,你没有告诉它你想等待,它也不需要等待任何技术原因,因此它会立即返回您要求的数据。毕竟,在它被提交之前,update只有一种存在——它仍然可能发生,也可能不会发生。

如果 PostgreSQL 总是在这里等待,那么您很快就会遇到一次只有一个连接可以对数据库执行任何操作的情况。对于性能来说并不漂亮,并且在绝大多数情况下完全没有必要。

如果你想等待并发UPDATE(或DELETE),你会使用SELECT ... FOR SHARE. (但请注意,这不适用于INSERT)。


细节:

SELECT没有FOR UPDATEorFOR SHARE子句不采用任何行级锁。所以它可以看到当前提交的行,并且不受任何可能正在修改该行的正在进行的事务的影响。这些概念在文档的 MVCC 部分中进行了解释。一般的想法是 PostgreSQL 是写时复制的,其版本控制允许它根据事务或语句在启动时“看到”的内容返回正确的副本——PostgreSQL 称之为“快照”。

在默认READ COMMITTED隔离中,快照是在语句级别拍摄的,因此,如果您SELECT是一行,COMMIT则从另一个事务中对其进行更改,并且SELECT即使在一个事务中,您也会再次看到不同的值。SNAPSHOT如果您不想在事务开始后看到提交的更改,则可以使用隔离,或者使用SERIALIZABLE隔离来进一步保护免受某些类型的事务相互依赖。

请参阅文档中的事务隔离章节

如果您希望SELECT等待正在进行的事务提交或回滚对所选行的更改,则必须使用SELECT ... FOR SHARE. 这将阻塞由UPDATE或获取的锁,DELETE直到获取锁的事务回滚或提交。

INSERT但不同的是 - 元组在提交之前不存在于其他事务中。等待并发INSERTs 的唯一方法是获取EXCLUSIVE表级锁,这样您就知道在您读取表时没有其他人在更改表。通常需要这样做意味着您在应用程序中存在设计问题 - 您的应用程序不应该关心是否还有未提交insert的 s 仍在运行中。

请参阅文档的显式锁定章节

于 2014-04-09T06:06:30.940 回答
1

PostgreSQL 的 MVCC实现中,原则是读不阻塞写,反之亦然。手册:

使用 MVCC 并发控制模型而不是锁定的主要优点是,在 MVCC 中,为查询(读取)数据而获得的锁与为写入数据而获得的锁不会冲突,因此读取永远不会阻塞写入,写入永远不会阻塞读取。即使通过使用创新的可 序列化快照隔离(SSI) 级别提供最严格的事务隔离级别,PostgreSQL 也保持此保证。

每个事务只看到(大部分)事务开始之前已提交的内容。

这并不意味着没有锁定。一点也不。对于许多操作,需要获取各种锁。并应用各种策略来解决可能的冲突。

于 2014-04-08T22:32:15.703 回答