使用外键约束更新行的锁定

38k*_*kun 6 postgresql locking

我尝试执行相同的UPDATE查询两次,如下所示。

第一次事务没有锁,但我可以在第二次查询后看到行锁。

架构:

test=# \d t1
                 Table "public.t1"
 Column |  Type   | Collation | Nullable | Default 
--------+---------+-----------+----------+---------
 i      | integer |           | not null | 
 j      | integer |           |          | 
Indexes:
    "t1_pkey" PRIMARY KEY, btree (i)
Referenced by:
    TABLE "t2" CONSTRAINT "t2_j_fkey" FOREIGN KEY (j) REFERENCES t1(i)

test=# \d t2
                 Table "public.t2"
 Column |  Type   | Collation | Nullable | Default 
--------+---------+-----------+----------+---------
 i      | integer |           | not null | 
 j      | integer |           |          | 
 k      | integer |           |          | 
Indexes:
    "t2_pkey" PRIMARY KEY, btree (i)
Foreign-key constraints:
    "t2_j_fkey" FOREIGN KEY (j) REFERENCES t1(i)
Run Code Online (Sandbox Code Playgroud)

现有数据:

test=# SELECT * FROM t1 ORDER BY i;
 i | j 
---+---
 1 | 1
 2 | 2
(2 rows)

test=# SELECT * FROM t2 ORDER BY i;
 i | j | k 
---+---+---
 3 | 1 |  
 4 | 2 |  
(2 rows)
Run Code Online (Sandbox Code Playgroud)

UPDATE 查询和行锁状态:

test=# BEGIN;
BEGIN
test=# UPDATE t2 SET k = 123 WHERE i = 3;
UPDATE 1
test=# SELECT * FROM t1 AS t, pgrowlocks('t1') AS p WHERE p.locked_row = t.ctid;
 i | j | locked_row | locker | multi | xids | modes | pids 
---+---+------------+--------+-------+------+-------+------
(0 rows)

test=# UPDATE t2 SET k = 123 WHERE i = 3;
UPDATE 1
test=# SELECT * FROM t1 AS t, pgrowlocks('t1') AS p WHERE p.locked_row = t.ctid;
 i | j | locked_row | locker | multi |   xids   |       modes       | pids 
---+---+------------+--------+-------+----------+-------------------+------
 1 | 1 | (0,1)      | 107239 | f     | {107239} | {"For Key Share"} | {76}
(1 row)

test=# 
Run Code Online (Sandbox Code Playgroud)

为什么 postgres 只在第二次尝试获得行锁?

顺便说一下,更新列 t2.j 的查询立即在 t1 行上创建新锁(ForKeyShare)。这种行为是有道理的,因为 t2.j 有外键约束引用 t1.i。但上面的查询似乎不是。

谁能解释一下这个锁?

PostgreSQL 版本:9.6.3

38k*_*kun 4

好的,我明白了。

https://engineering.nordeus.com/postgres-locking-revealed/

这是 Postgres 中存在的优化。如果锁定管理器可以从第一个查询中确定外键没有更改(更新查询中未提及或设置为相同的值),它将不会锁定父表。但在第二个查询中,它将按照文档中描述的方式运行(它将在 ROW SHARE 锁定模式下锁定父表,并在 FOR SHARE 模式下锁定引用行)

看来 MySQL 对于外键锁更加明智,因为相同的UPDATE查询不会在 MySQL 上进行此类锁定。

  • 我希望 Postgres 会有这样的行为。但是 T1 上引用的行是使用 FOR KEY SHARE 模式锁定的,并且此锁定模式会阻止同一行上的“SELECT FOR UPDATE”。因此,只有在 Postgres 上才会发生死锁。但我知道避免这种情况的方法。“SELECT FOR NO KEY UPDATE”不会与“FOR KEY SHARE”发生冲突。不管怎样谢谢你的建议。 (2认同)