所以,我对Postgresql中的外键约束处理感到困惑。 (版本8.4.4,它的价值)。
我们有几张桌子,下面有点匿名:
device:
(id, blah, blah, blah, blah, blah x 50)…
primary key on id
whooooole bunch of other junk
device_foo:
(id, device_id, left, right)
Foreign key (device_id) references device(id) on delete cascade;
primary key on id
btree index on 'left' and 'right'
所以我开始使用两个数据库窗口来运行一些查询。
db1> begin; lock table device in exclusive mode;
db2> begin; update device_foo set left = left + 1;
db2连接阻止。
对我而言,对device_stuff上的“left”列的更新应该受到设备表上的活动的影响,这似乎很奇怪。但它是。事实上,如果我回到db1:
db1> select * from device_stuff for update;
*** deadlock occurs ***
pgsql日志包含以下内容:
blah blah blah deadlock blah.
CONTEXT: SQL statement "SELECT 1 FROM ONLY "public"."device" x WHERE "id" OPERATOR(pg_catalog.=) $1 FOR SHARE OF X: update device_foo set left = left + 1;
我想我有两个问题:第一个是我不明白这种锁定发生的确切机制。我有几个有用的查询来查询pg_locks以查看语句调用的锁类型,但是当我单独运行update device_foo
命令时,我无法观察到这种特定的锁定。 (也许我做错了。)我也找不到任何关于外键约束检查的锁获取行为的文档。我所拥有的只是一条日志消息。我是否可以从中推断出对行的任何更改都将获取对外键控的所有表的更新锁定?
第二个问题是,我想找到一些方法让它不会发生这样的事情。我最终在实际应用程序中偶尔出现死锁。我希望能够运行影响device_foo
上所有行的大更新语句,而不会在设备表上获得大锁。 (在device
表格中有一个 很多 的访问权限,这是一种昂贵的锁定。)
答案 0 :(得分:2)
语句lock table device in exclusive mode
对表格进行了非常严格的锁定("exclusive mode")。将具有外键的表修改为父表会在父表上进行相当无害的共享锁定(例如,在引用它的行可能正在更新时,不能截断表。)
实际上,现在尝试一下,我无法重现您的锁定行为(在8.4.4上)。我做了:
create table device(device_id serial primary key, value text not null);
create table device_foo(device_foo_id serial primary key, device_id int not null references device(device_id) on delete cascade, value text not null);
insert into device(value) values('FOO'),('BAR'),('QUUX');
insert into device_foo(device_id, value) select device_id, v.value from (values('mumble'),('grumble'),('fumble')) v(value), device;
然后在两个并发的连接中我做了:
<1>=# begin; lock table device in exclusive mode;
<2>=# begin; update device_foo set value = value || 'x';
在我看来,这相当于你正在做的事情,但我没有得到第二个会话锁定 - 它会立即按照预期给出“更新9”。 正如您所期望的那样,将插入device_foo
块,设置device_id
列的更新语句也是如此。我可以在db2会话中的db1会话中看到pg_locks
中的ExclusiveLock。如果我执行“select * from device for share”,它也会阻塞,这是你在死锁错误中看到的语句。如果我从db1连接执行“select * from device_foo for update”而db2被阻止尝试更新device_foo中的device_id列,我也不会遇到死锁。
更新行会将该行标记为已锁定,但该锁定在pg_locks中不可见。它还会锁定表,以锁定任何试图在更新其中一行时删除/截断/重新索引表的人。
要锁定device
表以防止并发更新,您可能需要一种不太严格的锁定模式。 manual表示此类活动的“共享行独占”。虽然这只是“独家”的一个级别,但它与“选择...共享”声明兼容。
所以,真正的,开放的问题是 - 发出“选择...分享”查询的内容是什么? :-S它看起来像一个旨在断言外键完整性的声明,但我无法重现它。
答案 1 :(得分:0)
以独占模式锁定表意味着没有进程可以读取该表,并且检查外键需要读取表设备。