Postgresql:从外键约束评估中隐式获取锁
所以,我对 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;
我想我有两个问题:第一是我不理解发生这种锁定的精确机制。我有几个有用的查询来查询 pg_locks 以查看语句调用的锁类型,但是当我运行 update device_foo
命令时,我无法观察到这种特定类型的锁定隔离。 (不过,也许我做错了什么。)我也找不到任何有关外键约束检查的锁获取行为的文档。我所拥有的只是一条日志消息。我是否可以从中推断出,对行的任何更改都将获得其外键针对的所有表的更新锁?
第二个问题是我想找到一些方法来避免这种情况发生。我在实际应用程序中偶尔会遇到死锁。我希望能够运行影响 device_foo
上所有行的大型更新语句,而无需获取设备表上的大锁。 (大量在 device
表中进行访问,并且获取这种锁的成本很高。)
So, I'm being confused about foreign key constraint handling in Postgresql. (version 8.4.4, for what it's worth).
We've got a couple of tables, mildly anonymized below:
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'
So I set out with two database windows to run some queries.
db1> begin; lock table device in exclusive mode;
db2> begin; update device_foo set left = left + 1;
The db2 connection blocks.
It seems odd to me that an update of the 'left' column on device_stuff should be affected by activity on the device table. But it is. In fact, if I go back to db1:
db1> select * from device_stuff for update;
*** deadlock occurs ***
The pgsql log has the following:
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;
I suppose I've got two issues: the first is that I don't understand the precise mechanism by which this sort of locking occurs. I have got a couple of useful queries to query pg_locks to see what sort of locks a statement invokes, but I haven't been able to observe this particular sort of locking when I run the update device_foo
command in isolation. (Perhaps I'm doing something wrong, though.) I also can't find any documentation on the lock acquisition behavior of foreign-key constraint checks. All I have is a log message. Am I to infer from this that any change to a row will acquire an update lock on all the tables which it's foreign-keyed against?
The second issue is that I'd like to find some way to make it not happen like that. I'm ending up with occasional deadlocks in the actual application. I'd like to be able to run big update statements that impact all rows on device_foo
without acquiring a big lock on the device table. (There's a lot of access going on in the device
table, and it's kind of an expensive lock to get.)
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
语句
以独占模式锁定表设备
对表进行非常严格的锁定 ("独占模式")。修改具有父表外键的表需要在父表上使用相当无害的共享锁(例如,当引用表的行可能被更新时,您不能截断表)。实际上,现在尝试一下,我无法重现您的锁定行为(在 8.4.4 上,就像您一样)。我做了:
然后在两个并发连接中我做了:
这在我看来相当于你正在做的事情,但我没有得到第二个会话锁定 - 它立即按预期给出“UPDATE 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
表以防止并发更新,您可能需要不太严格的锁定模式。 手册 建议对这种类型“共享行独占”活动。尽管这仅比“独占”低一级,但它与“选择...共享”语句兼容。所以实际上,悬而未决的问题是——什么发出了“选择...共享”查询? :-S 它看起来确实像是一个旨在断言外键完整性的声明,但我无法重现它。
The statement
lock table device in exclusive mode
takes a very restrictive lock on the table ("exclusive mode"). Modifying a table that has a foreign key onto a parent table takes a fairly innocuous share lock on the parent table (you can't truncate a table while rows referencing it are potentially being updated, for example).Actually, trying it now, I can't reproduce your locking behaviour (on 8.4.4 as you are). I did:
And then in two concurrent connections I did:
This appears to me to be equivalent to what you're doing, but I don't get the second session locking- it immediately gives "UPDATE 9" as expected. Inserting into
device_foo
blocks, as you'd expect, and so does an update statement setting thedevice_id
column. I can see the ExclusiveLock inpg_locks
from the db1 session in the db2 session. It also blocks if I do "select * from device for share", which is the statement you're seeing in the deadlock error. I also don't get a deadlock if I do a "select * from device_foo for update" from the db1 connection while db2 is blocked trying to update the device_id column in device_foo.Updating a row does mark the row as locked, but that lock isn't visible in pg_locks. It does also take a lock on the table to lock out anyone trying to drop/truncate/reindex the table while one of its rows is being updated.
To lock the
device
table against concurrent updates, you may want a less strict locking mode. The manual suggests "share row exclusive" for this kind of activity. Although this is just one level down from "exclusive" it is compatible with a "select ... for share" statement.So really, the open question is--- what's issuing that "select ... for share" query? :-S It does look like a statement intended to assert foreign-key integrity, but I can't reproduce it.
以独占模式锁定表意味着没有进程可以读取该表,并且检查外键需要读取表设备。
Locking table in exclusive mode means that none process can read that table, and checking foreign key needs reading the table device.