涉及子选择和外键的 Postgres 竞争条件

cpp*_*ner 13 sql postgresql concurrency foreign-keys subquery

我们有 2 个表定义如下

CREATE TABLE foo (
  id BIGSERIAL PRIMARY KEY,
  name TEXT NOT NULL UNIQUE
);

CREATE TABLE bar (
  foo_id BIGINT UNIQUE,
  foo_name TEXT NOT NULL UNIQUE REFERENCES foo (name)
);

Run Code Online (Sandbox Code Playgroud)

我注意到在同时执行以下两个查询时

INSERT INTO foo (name) VALUES ('BAZ')
Run Code Online (Sandbox Code Playgroud)
INSERT INTO bar (foo_name, foo_id) VALUES ('BAZ', (SELECT id FROM foo WHERE name = 'BAZ'))
Run Code Online (Sandbox Code Playgroud)

在某些情况下,最终可能会在barwhere foo_idis 中插入一行NULL。这两个查询由两个完全不同的进程在不同的事务中执行。

这怎么可能?我希望第二条语句要么由于外键违规而失败(如果记录foo不存在),要么以非空值foo_id(如果是)成功。

是什么导致了这种竞争条件?是由于子选择,还是由于检查外键约束的时间?

我们使用隔离级别“已提交读”和 postgres 10.3 版。

编辑

我认为这个问题并不清楚是什么让我感到困惑。问题是关于在执行单个语句期间如何以及为什么观察到数据库的 2 个不同状态。subselect 观察到 foo 中的记录不存在,而 fk 检查认为它存在。如果只是没有规则阻止这种竞争条件,那么这本身就是一个有趣的问题 - 为什么不能使用事务 ID 来确保对两者都观察到相同的数据库状态?

Lau*_*lbe 10

中的子选择INSERT INTO bar无法看到同时插入的新行,foo因为后者尚未提交。

但是到执行检查外键约束的查询时,INSERT INTO foo已提交,因此外键约束不会报告错误。

解决此问题的一种简单方法是将REPEATABLE READ隔离级别用于INSERT INT bar. 然后外键检查使用与 相同的快照INSERT,它不会看到新提交的行,并且会抛出约束违规错误。

  • @cppbeginner“为什么观察到数据库的两种不同状态”-因为您正在使用“读已提交”隔离级别。此级别不足以阻止您所看到的情况。 (4认同)