我想更好地理解postgres中的锁定机制。
让我们说树可以有苹果(通过苹果桌上的外键)。似乎在选择树时进行更新锁定是在苹果上获得的。但是,即使其他人已经锁定了这个苹果,操作也不会被阻止。
为什么会这样?
P.S。请勿建议删除"选择更新"。
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"
答案 0 :(得分:9)
只是一个疯狂的猜测:你遇到了与实现细节相关的问题......
具体来说,您的select tree for update
语句获取树上的独占锁。并且update apples
语句获得对相关苹果的独占锁定。
当您在Apple上运行更新时,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释放树上的锁,以便最终确定其对apple的更新语句,但事务2正在等待事务1完成以获得对苹果的锁定,以便开始关于苹果的更新声明。
结果,Postgres陷入僵局。
答案 1 :(得分:-3)
看起来索引锁在整个事务期间都没有保留。我认为主要问题是事务1执行相同的UPDATE
两次,但它需要获取更多锁来执行第二次UPDATE
。
根据docs,索引锁只能保持很短的时间。与数据锁不同,它们在事务完成之前不会被保留。让我们更详细地看一下时间表。
事务1执行第一个UPDATE
。这会在apples
中的行上获取行级锁定。在操作期间,它还获取trees
中索引的锁定。事务尚未提交,因此行级数据锁仍由转换1保留。但是,trees
上的索引锁定会立即释放。不确定为什么Postgres会为所有索引类型执行此操作。
事务2出现并锁定trees
以进行更新。这会锁定数据和索引。这不会阻止,因为事务1已经释放了索引锁。这一次,两个锁都保持到事务结束。不知道为什么在释放另一个索引时保持这个索引锁定。
交易1返回并再次尝试UPDATE
。锁定apples
很好,因为它已经拥有它。但是,trees
上的锁定会阻止,因为事务2已经存在。
在事务2中添加UPDATE
使其在事务1上等待,从而导致死锁。
修改强>
我现在回来调查一下,我已经安装了Postgres。这实际上很奇怪。在提交事务2之后我查看了pg_locks
。
交易1具有以下锁定:
事务2具有以下锁(以及许多其他不相关的锁):
交易2也在等待在交易1上获得股票锁定。
有趣的是,two transactions can hold a RowExclusive lock on the same table。但是,Exclusive锁与Share冲突,因此Transaction 2正在等待Transaction 1的事务ID。 docs提及事务锁定作为等待另一个事务的方式。因此,看起来交易2虽然已经提交,但仍在等待交易1。
当事务1继续时,它想要在事务2上获取共享锁,这会产生死锁。为什么要在Transaction 2上获得共享锁?对此不太确定。 The docs提示pg_locks
中未提供此信息。我猜这与MVCC有关,但对我来说仍然是一个谜。