4

所以,我对 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 上“左”列的更新应该受到设备表上的活动的影响,这似乎很奇怪。但它是。事实上,如果我回到 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;

我想我有两个问题:第一个是我不了解发生这种锁定的确切机制。update device_foo我有几个有用的查询来查询 pg_locks 以查看语句调用的锁类型,但是当我单独运行命令时,我无法观察到这种特定类型的锁。(不过,也许我做错了什么。)我也找不到任何关于外键约束检查的锁定获取行为的文档。我只有一条日志消息。我是否可以从中推断出对行的任何更改都将在它所针对的所有表上获得更新锁?

第二个问题是我想找到一些方法让它不会像那样发生。我在实际应用程序中偶尔会出现死锁。我希望能够运行影响所有行的大更新语句,device_foo而无需在设备表上获得大锁。(表中有很多访问权限device,而且这是一种昂贵的锁。)

4

2 回答 2

3

该语句lock table device in 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”。如您所料,插入到块中,设置列的更新语句也是如此。我可以在 db2 会话中从 db1 会话中看到 ExclusiveLock。如果我执行“select * from device for share”,它也会阻塞,这是您在死锁错误中看到的语句。如果我从 db1 连接执行“select * from device_foo for update”,而 db2 被阻止尝试更新 device_foo 中的 device_id 列,我也不会遇到死锁。device_foodevice_idpg_locks

更新一行确实将该行标记为已锁定,但该锁在 pg_locks 中不可见。它还需要对表进行锁定,以锁定任何尝试删除/截断/重新索引表的人,同时更新其中的一行。

要锁定device表以防止并发更新,您可能需要一种不太严格的锁定模式。该手册建议此类活动“共享行独占”。尽管这仅比“独占”低一级,但它与“select ... for share”语句兼容。

所以真的,悬而未决的问题是——是什么发出“选择...共享”查询?:-S 它看起来确实像是一个旨在断言外键完整性的声明,但我无法重现它。

于 2010-06-14T22:05:53.403 回答
0

以独占模式锁定表意味着没有进程可以读取该表,并且检查外键需要读取表设备。

于 2010-06-14T20:21:31.157 回答