Val*_*aty 4 postgresql deadlock subquery update where
我使用 PostgreSQL 和以下数据库架构:
CREATE TABLE plans (
slug VARCHAR(500) PRIMARY KEY
);
CREATE TABLE users (
id VARCHAR(16) PRIMARY KEY,
org_id VARCHAR(16) NOT NULL
);
CREATE TABLE orgs (
id VARCHAR(16) PRIMARY KEY,
plan_slug VARCHAR(500) NOT NULL,
last_write_at DOUBLE PRECISION
);
Run Code Online (Sandbox Code Playgroud)
就我而言,我想编写一个查询来更新某些组织plan_slug并保护它免受其他可能的并发更新的影响。为此,我SELECT在子查询中使用 a 并按FOR UPDATE特定顺序锁定行以避免死锁。就像下面的查询一样:
UPDATE orgs
SET plan_slug = 'plan_1'
WHERE id = ANY(
SELECT subquery_orgs.id
FROM orgs AS subquery_orgs
JOIN users ON users.org_id = subquery_orgs.id
WHERE users.id = ANY('{user_1, user_2, user_3}')
ORDER BY subquery_orgs.id
FOR UPDATE
);
Run Code Online (Sandbox Code Playgroud)
我注意到,如果此请求需要很长时间才能运行,则很可能与尝试更新组织的另一个查询last_write_at(已由第一个查询更新的组织)发生冲突。
如下:
UPDATE orgs
SET last_write_at = 999
FROM plans
WHERE orgs.id = 'org_1';
Run Code Online (Sandbox Code Playgroud)
查询就会成功。但是如果我在查询的子句plan_slug中添加 the ,它总是会更新失败。WHEREPostgre 返回UPDATE 0.
查询如下:
UPDATE orgs
SET last_write_at = 999
FROM plans
WHERE orgs.id = 'org_1'
AND plans.slug = orgs.plan_slug;
Run Code Online (Sandbox Code Playgroud)
据我了解,流程应该是:
plan_slug行已锁定。plan_slug更改,但表中存在新的plans,第二个查询应该成功。plan_slug那么为什么当我将 the 添加到子句时查询无法更新WHERE?
在步骤 4 之前,您对该过程的总结是正确的。锁定后查询不会重新启动;读取修改后的行版本并替换旧的行版本。在采取锁定之前,已发生表联接,新的行版本使联接变得无意义,并且行被丢弃。
这是我对事件的修订版本:
第一个查询开始执行。中的行orgs被锁定。
第二个查询开始执行。
A。从和UPDATE读取旧行版本(忽略#1,因为它还没有)。orgsplansCOMMIT
b. 对和UPDATE执行联接并有资格构建目标行列表。orgsplansorgs.id
C。UPDATE尝试将目标行FOR NO KEY SHARE和块锁定在第一个查询的FOR UPDATE锁上。
第一个查询COMMIT。
第二个查询解除阻止并注意到它所定位的行已更改。
A。读取新的行版本并替换联接中的现有行版本。
b. 重新评估条件,现在连接条件失败,因为orgs.plan_slug已更改并且不再plans与之前连接的行匹配。这些行将被丢弃。
第二个查询没有找到要使用 定位的行UPDATE。
以图形方式显示它:
第二个查询以连接开始,如下所示:
| ID | 计划_slug | 最后写入时间 | 蛞蝓 |
|---|---|---|---|
| 用户_1 | 计划_1 | 第888章 | 计划_1 |
第一个查询完成,orgs.plan_slug从 更改plan_1为plan_2。
第二个查询继续,读取orgs行版本,并将其连接状态更改为:
| ID | 计划_slug | 最后写入时间 | 蛞蝓 |
|---|---|---|---|
| 用户_1 | 计划_2 | 第888章 | 计划_1 |
第二个查询现在重新评估其WHERE条件。呃哦,plan_2!= plan_1,行被丢弃。
FROM plans在你的UPDATE last_write_at.... 老实说,我不明白你的查询的目的是什么;中的列plans没有以任何有意义的方式引用。我认为那里发生了更多事情,为了简化问题而删除了这些内容。也许您可以更新问题以阐明为什么会这样。REPEATABLE READ对您的事务使用隔离级别UPDATE last_write_at...。在该级别,您不会更新任何行,而是会收到错误ERROR: could not serialize access due to concurrent update。您可以中止并重试事务,因为知道它正在获取新数据。SELECT .. FOR UPDATE在事务中使用 previous来锁定 before 的行UPDATE。然后UPDATE永远不必应对从其下方发生变化的行。在 PL/pgSQL 中,您可以使用PERFORM 1 ... FOR UPDATE代替来避免输出行。举个例子:
BEGIN;
SELECT id FROM orgs WHERE id='org_1' FOR UPDATE;
UPDATE orgs SET last_write_at = 999 FROM plans
WHERE orgs.id = 'org_1' AND plans.slug = orgs.plan_slug;
COMMIT;
Run Code Online (Sandbox Code Playgroud)
PERFORM 1 ...| 归档时间: |
|
| 查看次数: |
358 次 |
| 最近记录: |