让我们在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中的显式提交被执行。
答案 0 :(得分:1)
您描述的行为在任何事务性关系数据库中都是正常的和预期的。
如果PostgreSQL向您显示第一个edited
的值SELECT
,那么这样做是错误的 - 这被称为"脏读",并且是数据库中的坏消息。
PostgreSQL将被允许在SELECT
等待,直到您提交或回滚为止,但SQL标准并不要求它,您还没有告诉它您要等待,并且它不必等待任何技术原因,因此它会立即返回您要求的数据。毕竟,在它提交之前,update
只存在 - 它仍然可能会或可能不会发生。
如果PostgreSQL总是在这里等待,那么你很快就会遇到这样的情况:一次只有一个连接可以对数据库做任何事情。性能不佳,绝大部分时间完全没用。
如果您要等待并发UPDATE
(或DELETE
),请使用SELECT ... FOR SHARE
。 (但请注意,这对INSERT
)不起作用。
详细说明:
没有SELECT
或FOR 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
。这将阻止UPDATE
或DELETE
锁定,直到锁定的事务回滚或提交为止。
INSERT
是不同的 - 元组在提交之前不会存在于其他事务中。等待并发INSERT
的唯一方法是采用EXCLUSIVE
表级锁定,因此您知道在读取表时没有其他人正在更改表。通常需要这样做意味着您在应用程序中遇到了设计问题 - 如果有未提交的insert
仍然在飞行中,您的应用程序不应该关注。
答案 1 :(得分:0)
在PostgreSQL's MVCC实现中,原则是读取不会阻止写入,反之亦然。 Per documentation:
使用MVCC并发控制模型的主要优点 而不是锁定是在获取用于查询的MVCC锁中 (读取)数据与为写入数据而获取的锁不冲突, 因此阅读永远不会阻止写作和写作从不阻止阅读。 PostgreSQL即使在提供最严格的保证时也能保持这种保证 通过使用创新的交易隔离水平 可序列化快照隔离(SSI)级别。
每笔交易只会(大部分)看到交易开始前提交的内容。
那不意味着没有锁定。一点也不。对于许多操作,获得各种锁。并且应用各种策略来解决可能的冲突。