暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

POSTGRESQL:理解死锁

飞象数据 2022-01-21
1478

许多人可能看到PostgreSQL发出以下错误消息:"ERROR: deadlock detected"
。但这到底意味着什么呢?我们如何防止死锁,如何重现问题?让我们深入研究PostgreSQL的锁机制,了解死锁死锁超时的真正含义。

死锁是如何产生的?

许多人都想了解什么是死锁以及死锁是如何发生的。他们还想了解如何避免死锁,以及软件开发人员可以做些什么。

如果您想了解死锁是如何发生的,只需要一个包含两行的表。这足以解释死锁的基本原理。

以下是一些易于使用的示例数据:

1

test=# CREATE TABLE t_data (id int, data int);

2

CREATE TABLE

3

test=# INSERT INTO t_data VALUES (1, 100), (2, 200);

4

INSERT 0 2

5

test=# TABLE t_data;

6

id | data

7

----+------

8

1 | 100

9

2 | 200

10

(2 rows)

复制

问题的关键是,如果数据以不同的顺序更新,事务可能必须等待彼此完成。打比方,如果事务1必须等待事务2,那完全可以。但是,如果事务1必须等待事务2,而事务2必须等待事务1,会发生什么情况呢?在这种情况下,系统有两种选择:

  • 无限地等待

  • 中止一个事务并提交另一个事务。

由于不能无限等待,PostgreSQL将在一段时间后中止其中一个事务(死锁超时)。发生的情况如下:

事务1事务2解释
BEGIN;BEGIN;
UPDATE t_data SET data = data * 10 WHERE id = 1 RETURNING *;UPDATE t_data SET data = data * 10 WHERE id = 2 RETURNING *;没有问题
UPDATE t_data SET data = data * 10 WHERE id = 2 RETURNING *;
必须等待事务2释放包含id=2的行上的锁
… 等待 …UPDATE t_data SET data = data * 10 WHERE id = 1 RETURNING *;想要锁定由事务id锁定的行:现在两个都应该等待
… 死锁超时 …… 死锁超时 …PostgreSQL等待(死锁超时)并在此超时后触发死锁检测(不是立即)
update proceeds: “UPDATE 1”ERROR: deadlock detected事物2终止
COMMIT;
其余的正常提交

我们将看到的错误消息是:

1

ERROR: deadlock detected

2

DETAIL: Process 70725 waits for ShareLock on transaction 891717; blocked by process 70713.

3

Process 70713 waits for ShareLock on transaction 891718; blocked by process 70725.

4

HINT: See server log for query details.

5

CONTEXT: while updating tuple (0,1) in relation "t_data"

复制

原因是事务必须等待对方。如果两个事务发生冲突,PostgreSQL不会立即解决问题,而是等待死锁超时,然后触发死锁检测算法来解决问题。

为什么PostgreSQL需要等待一段时间才介入并修复问题?原因是死锁检测非常耗资源,因此不立即触发死锁是有意义的。这里的默认值是1秒,它足够高,可以避免无意义的死锁检测,但仍然足够有效,可以及时地解决问题。

如何修复和避免死锁

最重要的是要知道:没有神奇的配置参数来解决这个问题。问题不取决于配置。这取决于操作的执行顺序。换句话说,如果不了解应用及其底层操作,就无法根本地修复它。

唯一可以解决此问题的方法是更改执行顺序,如下一个清单所示:

1

test=# SELECT * FROM t_data ;

2

id | data

3

----+------

4

1 | 1000

5

2 | 2000

6

(2 rows)

复制

这是在测试之前应该看到的数据。我们可以看到如果两个事务以不同的顺序执行,会发生什么:

事务1事务2解释
BEGIN;

UPDATE t_data SET data = data * 10 

WHERE id = 1 RETURNING *;

id | data--+------

1  | 10000

BEGIN;

UPDATE t_data SET data = data * 10 

WHERE id = 1 RETURNING *;


UPDATE t_data SET data = data * 10 

WHERE id = 2 RETURNING *; 

id | data--+-------

2  | 20000

… 等待…
COMMIT;… 等待…

UPDATE t_data SET data = data * 10 

WHERE id = 1 RETURNING *; 

id | data--+--------

1  | 100000

重新读取该值并使用新提交的条目

UPDATE t_data SET data = data * 10 

WHERE id = 2 RETURNING *;

id | data--+--------

2  | 200000

重新读取该值并使用新提交的条目

COMMIT;

在这种情况下,不存在死锁。然而,在实际工作场景中,很难简单地交换执行顺序。这就是为什么这更多的是一个理论上的解决方案,而不是一个实际的解决方案。但是,没有其他解决死锁问题的方法。在出现死锁的情况下,了解如何预防死锁是最好的治疗方法。

最后 ...

真的很重要。在这方面,死锁并不是唯一的问题。性能可能同样重要,因此处理与性能相关的锁也是有意义的。

文章转载自飞象数据,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论