PostgreSQL锁定机制中的错误或对机制的误解

Ami*_*ron 8 postgresql locking transactions

我们遇到了PostgreSQL 9.0.12锁定机制的问题.

这是我们重现问题的最小代码:

脚本

Transaction 1      Transaction 2
BEGIN              BEGIN
......             select trees for update;                
update apples;      
--passes
update apples;    
-- stuck!      
Run Code Online (Sandbox Code Playgroud)

重现代码:如果你想在你的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);
Run Code Online (Sandbox Code Playgroud)

打开两个psql shell:

在shell 1上:

BEGIN;
    SELECT id FROM trees WHERE id = 1 FOR UPDATE;    
Run Code Online (Sandbox Code Playgroud)

在shell 2上:

BEGIN;
UPDATE apples SET id = id WHERE id = 1;
UPDATE apples SET id = id WHERE id = 1;
Run Code Online (Sandbox Code Playgroud)

苹果的第二次更新将卡住,似乎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
Run Code Online (Sandbox Code Playgroud)

我们误解了什么或者是postgres中的错误吗?

alv*_*rre 9

没有错误,我认为你不会误解任何事情; 你只是错过了几个难题.

外键使用行级锁定在内部实现; 从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 UPDATEto SELECT FOR NO KEY UPDATE(这比SELECT FOR UPDATE你说的更轻的模式可能会更新元组,但你保证不修改主键并承诺不删除它),你应该看到它不会阻止.(另外,您可以在引用的行上尝试UPDATE,如果不修改主键,那么它也不会阻塞.)

这个9.3内容由你的补丁真正引入http://git.postgresql.org/gitweb/?p=postgresql.git;a=commit;h=0ac5ad5134f2769ccbaefec73844f8504c4d6182,我认为这是一个非常酷的黑客(提交如果你关心那种东西,那么消息还有一些细节.但要注意,不要使用9.3.4之前的版本,因为该补丁非常复杂,以至于一些严重的错误被忽视,我们最近才修复.

  • `UPDATE set id=id` 似乎不太合理。你真的在更新主键的值吗?如果是这样,没有什么可以帮助你。你能在第一个会话中使用 FOR SHARE 而不是 FOR UPDATE 吗? (2认同)