6

我有一个表foo_bar和另一个表spam_eggsfb外键指向foo_bar. spam_eggs当删除相关行时,行将被级联spam_eggs.fb删除。

我正在使用 PostgreSQL。

在一个事务中,我曾经SELECT... FOR UPDATE锁定spam_eggs一行。在此事务期间,另一个事务已尝试DELETE FROM...foo_bar我的锁定行相关。这会触发错误,还是我锁定的行会导致查询阻塞,直到我的原始更新事务结束?

4

1 回答 1

14

试试看。打开psql并进行一些设置:

CREATE TABLE foo_bar(id integer primary key);
CREATE TABLE spam_eggs(
     foo_bar_id integer not null references foo_bar(id) on delete cascade
);
INSERT INTO foo_bar (id) VALUES (1),(2),(3),(4);
INSERT INTO spam_eggs(foo_bar_id) VALUES (1),(2),(3),(4);

然后打开另一个 psql 连接。BEGIN双方的交易。

  1. 在第一个(旧)会话中,运行SELECT 1 FROM spam_eggs WHERE foo_bar_id = 4 FOR UPDATE;
  2. 在第二个(新)会话中,运行DELETE FROM foo_bar WHERE id = 4;

您将看到第二个语句阻塞在第一个语句上。那是因为DELETEonfoo_bar级联到spam_eggs并尝试使用外键引用锁定行,以便它可以删除它。那把锁挡在SELECT ... FOR SHARE.

一般来说,尝试在所有这些情况下进行测试:

  • tx 是BEGIN ISOLATION LEVEL READ COMMITTED和第一个问题ROLLBACK
  • tx 是BEGIN ISOLATION LEVEL READ COMMITTED和第一个问题COMMIT
  • tx 是BEGIN ISOLATION LEVEL SERIALIZABLE和第一个问题ROLLBACK
  • tx 是BEGIN ISOLATION LEVEL SERIALIZABLE和第一个问题COMMIT

以确保您知道会发生什么。如果您在测试之前对预期发生的事情进行推理,这对您的学习也有好处。

在这种情况下,隔离级别READ COMMITTEDSERIALIZABLE隔离级别将表现相同。但是,如果您实际上执行了操作UPDATESELECT ... FOR UPDATE那么COMMIT它们的行为会有所不同;该READ COMMITTED版本将DELETE成功,而该SERIALIZABLE版本将失败:

regress=# BEGIN ISOLATION LEVEL SERIALIZABLE;
regress=# DELETE FROM foo_bar WHERE id = 4;                                                                                                                                                                                                    
ERROR:  could not serialize access due to concurrent update                                                                                                                                                        
CONTEXT:  SQL statement "DELETE FROM ONLY "public"."spam_eggs" WHERE $1 OPERATOR(pg_catalog.=) "foo_bar_id""
于 2012-08-20T03:19:12.557 回答