postgres中应该阻止查询的锁定和事务

时间:2014-04-08 18:50:33

标签: postgresql concurrency locking mvcc

让我们在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中的显式提交被执行。

2 个答案:

答案 0 :(得分:1)

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

如果PostgreSQL向您显示第一个edited的值SELECT,那么这样做是错误的 - 这被称为"脏读",并且是数据库中的坏消息。

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

如果PostgreSQL总是在这里等待,那么你很快就会遇到这样的情况:一次只有一个连接可以对数据库做任何事情。性能不佳,绝大部分时间完全没用。

如果您要等待并发UPDATE(或DELETE),请使用SELECT ... FOR SHARE。 (但请注意,这对INSERT)不起作用。


详细说明:

没有SELECTFOR UPDATE子句的

FOR SHARE不会采用任何行级别锁定。因此,它会查看当前提交的行,并且不受可能正在修改该行的任何正在进行的事务的影响。概念在MVCC section of the docs中解释。一般的想法是PostgreSQL是写时复制的,其版本控制允许它根据事务或语句的内容返回正确的副本,并且#34;参见"在它开始的时候 - PostgreSQL称之为"快照"。

在默认的READ COMMITTED隔离快照是在语句级别进行的,因此如果您SELECT行,COMMIT从另一个事务更改它,SELECT再次,即使在一次转换中,你也会看到不同的价值观。如果您不希望在事务开始后看到提交的更改,则可以使用SNAPSHOT隔离,或SERIALIZABLE隔离以针对某些类型的事务依赖性添加进一步保护。

请参阅the transaction isolation chapter in the documentation

如果希望SELECT等待正在进行的事务提交或回滚对所选行的更改,则必须使用SELECT ... FOR SHARE。这将阻止UPDATEDELETE锁定,直到锁定的事务回滚或提交为止。

但是,

INSERT是不同的 - 元组在提交之前不会存在于其他事务中。等待并发INSERT的唯一方法是采用EXCLUSIVE表级锁定,因此您知道在读取表时没有其他人正在更改表。通常需要这样做意味着您在应用程序中遇到了设计问题 - 如果有未提交的insert仍然在飞行中,您的应用程序不应该关注

请参阅the explicit locking chapter of the documentation

答案 1 :(得分:0)

PostgreSQL's MVCC实现中,原则是读取不会阻止写入,反之亦然。 Per documentation:

  

使用MVCC并发控制模型的主要优点   而不是锁定是在获取用于查询的MVCC锁中   (读取)数据与为写入数据而获取的锁不冲突,   因此阅读永远不会阻止写作和写作从不阻止阅读。   PostgreSQL即使在提供最严格的保证时也能保持这种保证   通过使用创新的交易隔离水平   可序列化快照隔离(SSI)级别。

每笔交易只会(大部分)看到交易开始前提交的内容。

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