10

我想更好地了解一种锁定 postgres 的机制。

假设树可以有苹果(通过苹果表上的外键)。似乎在选择一棵树进行更新锁时是在苹果上获得的。但是,即使其他人已经锁定了这个苹果,操作也不会被阻止。

为什么会这样?

ps 请不要建议删除“选择更新”。

设想

Transaction 1      Transaction 2
BEGIN              .
update apple;      .
.                  BEGIN
.                  select tree for update;
.                  update apple;
.                  --halts because of the other transaction locking an apple
update apple;      .
-- deadlock        .
                   COMMIT
                   --transaction succeeds

代码

如果您想在您的 postgres 中尝试它 - 这是您可以复制/粘贴的代码。

我有以下数据库架构

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);

有两个简单的交易。一个是更新苹果,第二个是锁定一棵树并更新一个苹果。

BEGIN;
    UPDATE apples SET id = id WHERE id = 1;
    -- run second transaction in paralell
    UPDATE apples SET id = id WHERE id = 1;
COMMIT;

BEGIN;
    SELECT id FROM trees WHERE id = 1 FOR UPDATE;
    UPDATE apples SET id = id WHERE id = 1;
COMMIT;

当我运行它们时 - 在第一个事务的第二次更新时发生死锁。

ERROR:  deadlock detected
DETAIL:  Process 81122 waits for ShareLock on transaction 227154; blocked by process 81100.
Process 81100 waits for ShareLock on transaction 227153; blocked by process 81122.
CONTEXT:  SQL statement "SELECT 1 FROM ONLY "public"."trees" x WHERE "id" OPERATOR(pg_catalog.=) $1 FOR SHARE OF x"
4

2 回答 2

13

只是一个疯狂的猜测:您遇到了与实现细节相关的问题......

具体来说,您的select tree for update语句在树上获得了排他锁。并且这些update apples语句获得了相关苹果的排他锁。

当您在苹果上运行更新时,Postgres 的每行相关外键触发器会触发,以确保tree_id存在。我不记得它们的确切名称,但它们在目录中,并且在文档中明确或隐含地引用了它们,例如:

create constraint trigger ... on ... from ...

http://www.postgresql.org/docs/current/static/sql-createtrigger.html

无论如何,这些触发器将运行相当于以下内容的内容:

select exists (select 1 from trees where id = 1);

这就是您的问题:由于 的独占访问select for update使其等待事务 2 释放树上的锁以完成其对苹果的更新语句,但事务 2 正在等待事务 1 完成以获取锁关于苹果,以便开始其关于苹果的更新声明。

结果,Postgres 陷入僵局。

于 2013-09-02T10:32:11.143 回答
-3

看起来索引锁并没有在整个事务期间保持。我认为主要问题是事务 1 做了UPDATE两次同样的事情,但它需要获得更多的锁才能做第二次UPDATE

根据文档,索引锁只持有很短的时间。与数据锁不同,它们在事务完成之前不会被持有。让我们更详细地看一下时间线。

事务 1 执行第一个UPDATE。这会在 中的行上获取行级锁apples。在操作过程中,它还获取了索引中的锁trees。事务还没有提交,所以行级数据锁仍然由事务1持有。但是,索引锁trees立即被释放。不知道为什么 Postgres 对所有索引类型都这样做。

事务 2 出现并锁定trees更新。这会锁定数据和索引。这不会阻塞,因为事务 1 已经释放了索引锁。这一次,两个锁都被持有到事务结束。不知道为什么这个索引锁在另一个被释放时被持有。

事务 1 回来并UPDATE再次尝试。锁定apples很好,因为它已经有了它。trees然而,由于事务 2 已经拥有它,因此锁定在 上。

在事务 2 中添加UPDATE使其等待事务 1,从而导致死锁。

编辑:

既然我已经安装了 Postgres,我又回来调查这个问题。这真的很奇怪。pg_locks我在提交事务 2 后查看。

事务 1 具有以下锁:

  • apples_pkey 和 apples 上的 RowExclusive
  • 独占其 transactionid 和 virtualxid

事务 2 具有以下锁(以及许多其他不相关的锁):

  • Trees_pkey 上的 AccessShare
  • 树上的 RowShare
  • 独占其 transactionid 和 virtualxid
  • apples_pkey 和 apples 上的 RowExclusive
  • 在苹果中的一个元组上独占

事务 2 也在等待获取事务 1 的共享锁。

有趣的是,两个事务可以在同一张表上持有一个 RowExclusive 锁。但是,独占锁与共享锁冲突,因此事务 2 正在等待事务 1 的事务 id。文档提到事务锁作为等待其他事务的一种方式。因此,看起来事务 2 虽然已提交,但仍在等待事务 1。

当事务 1 继续时,它想在事务 2 上获取一个共享锁,这会产生死锁。为什么要获取事务 2 的共享锁?不太确定。文档提示此信息在pg_locks. 我猜这与 MVCC 有关,但对我来说仍然是个谜。

于 2013-09-03T00:49:08.117 回答