我们遇到了PostgreSQL 9.0.12锁定机制的问题。
这是我们再现问题的最小代码:
脚本
Transaction 1 Transaction 2
BEGIN BEGIN
...... select trees for update;
update apples;
--passes
update apples;
-- stuck!
再现代码:
如果你想在你的PostgreSQL中尝试它-这里有一个代码,你可以复制/粘贴。
我有以下数据库架构:
CREATE TABLE trees (
id integer primary key
);
create table apples (
id integer primary key,
tree_id integer references trees(id)
);
insert into trees values(1);
insert into apples values(1,1);
打开两个psql外壳:
在外壳1上:
BEGIN;
SELECT id FROM trees WHERE id = 1 FOR UPDATE;
在外壳2上:
BEGIN;
UPDATE apples SET id = id WHERE id = 1;
UPDATE apples SET id = id WHERE id = 1;
apples的第二次更新将被卡住,看起来shell 2的进程正在等待shell 1的事务完成。
relname |transactionid|procpid|mode |substr | age |procpid
-----------+-------------+-------+------------------+------------------------------------------+----------------+-------
| | 4911 | ExclusiveLock | <IDLE> in transaction | 00:05:42.718051|4911
| 190839904 | 4911 | ExclusiveLock | <IDLE> in transaction | 00:05:42.718051|4911
trees | | 4911 | RowShareLock | <IDLE> in transaction | 00:05:42.718051|4911
| | 5111 | ExclusiveLock | UPDATE apples SET id = id WHERE id = 1; | 00:05:21.67203 |5111
| 190839905 | 5111 | ExclusiveLock | UPDATE apples SET id = id WHERE id = 1; | 00:05:21.67203 |5111
apples_pkey| | 5111 | RowExclusiveLock | UPDATE apples SET id = id WHERE id = 1; | 00:05:21.67203 |5111
apples | | 5111 | RowExclusiveLock | UPDATE apples SET id = id WHERE id = 1; | 00:05:21.67203 |5111
trees | | 5111 | RowShareLock | UPDATE apples SET id = id WHERE id = 1; | 00:05:21.67203 |5111
trees | | 5111 | ShareLock | UPDATE apples SET id = id WHERE id = 1; | 00:05:21.67203 |5111
| | 2369 | ExclusiveLock | <IDLE> in transaction | 00:00:00.199268|2369
| | 2369 | ExclusiveLock | <IDLE> in transaction | 00:00:00.199268|2369
| | 5226 | ExclusiveLock | select pg_class.relname,pg_locks.transac | 00:00:00 |5226
我们是不是误解了什么,或者是博士后的一个错误?
最佳答案
没有虫子,我也不认为你误解了什么;你只是遗漏了一些拼图。
外键是使用行级锁定在内部实现的;从Postgres 8.1到9.2开始,每当更新引用表(在本例中为apples
)时,都会触发一个对引用表(SELECT FOR SHARE
)执行trees
的查询。因此,第一个事务中的SELECT FOR UPDATE
将阻塞第二个事务的引用完整性的SELECT FOR SHARE
。这就是导致第二个命令中的块的原因。
现在我听到你喊,“等等!为什么它挡在第二个命令上而不是第一个?这个解释很简单,真的——这只是因为有一个简单的优化,在没有修改键的时候跳过了内部的SELECT FOR SHARE
。但是,这很简单,因为如果您第二次更新一个元组,这个优化就不会启动,因为跟踪原始值比较困难。因此堵塞。
你可能还想知道我为什么说这是9.2,9.3是什么?它的主要区别在于在9.3中使用了SELECT FOR KEY SHARE
,这是一个新的、更轻的锁级别;它允许更好的并发性。如果您在9.3中尝试您的示例,并将SELECT FOR UPDATE
更改为SELECT FOR NO KEY UPDATE
(这是比SELECT FOR UPDATE
更轻量级的模式,该模式表示您可能要更新元组,但您承诺不修改主键并保证不删除它),则应该看到它不会阻塞。(此外,您可以尝试对引用行进行更新,如果不修改主键,则它也不会被阻止。)
这个9.3的东西是由你的一个补丁真正引入的http://git.postgresql.org/gitweb/?p=postgresql.git;a=commit;h=0ac5ad5134f2769ccbaefec73844f8504c4d6182,我认为这是一个非常酷的黑客(commit消息有更多的细节,如果你关心这类东西的话)。但是请注意,不要使用9.3.4之前的版本,因为这个补丁非常复杂,以至于一些严重的错误没有被注意到,我们只是最近才修复。