【问题标题】:PostgreSQL for update statementPostgreSQL 更新语句
【发布时间】:2014-09-30 09:16:21
【问题描述】:

PostgreSQL 已读取提交的隔离级别。现在我有一个由单个 DELETE 语句组成的事务,并且该删除语句有一个子查询,该子查询由一个 SELECT 语句组成,用于选择要删除的行。

是否必须在 select 语句中使用 FOR UPDATE 才能与其他事务不冲突?

我的想法如下:首先从表中读取相应的行,然后在第二步中删除这些行,因此另一个事务可能会干扰。

那么简单的 DELETE FROM myTable WHERE id = 4 语句呢?我还必须使用 FOR UPDATE 吗?

【问题讨论】:

    标签: postgresql transactions


    【解决方案1】:

    是不是我必须在select语句中使用FOR UPDATE来 与其他事务没有冲突?

    “与其他交易不冲突”对您意味着什么?您可以通过打开两个终端并在每个终端中执行语句来测试这一点。正确交错后,DELETE 语句将使“其他事务”(隔离级别设置为 READ COMMITTED 的事务)等待直到提交或回滚。

                                                     sandbox=# set transaction isolation level read committed;
                                                     SET
    
    sandbox=# select * from customer;
     date_of_birth 
    ---------------
     1996-09-29
     1996-09-28
    (2 rows)
    
    sandbox=# begin transaction;
    BEGIN
    sandbox=# delete from customer
    sandbox-# where date_of_birth = '1996-09-28';
    DELETE 1
                                                     sandbox=# update customer
                                                     sandbox-# set date_of_birth = '1900-01-01'
                                                     sandbox-# where date_of_birth = '1996-09-28';
                                                     (Execution pauses here, waiting for transaction in other terminal.)
    sandbox=# commit;
    COMMIT
    sandbox=# 
                                                     UPDATE 0
                                                     sandbox=# 
    

    请参阅下面的文档。

    那么简单的 DELETE FROM myTable WHERE id = 4 语句呢?做 我也必须使用 FOR UPDATE?

    没有DELETE . . . FOR UPDATE这样的声明。


    在阅读有关数据库更新的信息时,您需要对上下文敏感。 更新 可以表示对数据库的任何更改;它可以包括插入、删除和更新行。在下面引用的文档中,“locked as if for update”明确指的是 UPDATE 和 DELETE 语句等。

    Current docs

    FOR UPDATE 导致 SELECT 语句检索到的行 像更新一样被锁定。这可以防止它们被修改或 被其他事务删除,直到当前事务结束。那 是,其他尝试 UPDATE、DELETE、SELECT FOR UPDATE 的事务, SELECT FOR NO KEY UPDATE、SELECT FOR SHARE 或 SELECT FOR KEY SHARE of 这些行将被阻止,直到当前事务结束。为 UPDATE 锁定模式也被行上的任何 DELETE 获取,也被 修改某些列上的值的 UPDATE。目前, 为 UPDATE 情况考虑的列集是那些具有 可以在外键中使用的唯一索引(所以部分 不考虑索引和表达式索引),但这可能 未来的变化。此外,如果 UPDATE、DELETE 或 SELECT FOR UPDATE 从另一个事务已经锁定了一个或多个选定的行, SELECT FOR UPDATE 将等待另一个事务完成,并且 然后将锁定并返回更新的行(或者没有行,如果该行是 已删除)。

    【讨论】:

    • 我在想例如关于以下案例。假设我们有 2 个事务,其中包含一个删除语句,其中包含一个 select 语句。事务 1 执行选择语句以获取要删除的行。现在,事务 2 也执行 select 语句并检索相同的 ros。现在,事务 1 删除了行。事务 2 现在也尝试删除行,但失败了。在 select 子句中没有 FOR UPDATE 就不会发生这种情况吗?
    • “现在,事务 2 也执行 select 语句并检索 [sic] 相同的 ros [sic]” 不,它没有。它等待。在两个终端中自行测试。此外,执行不删除任何行的 DELETE 语句并不意味着 DELETE 语句失败。失败会引发错误。
    【解决方案2】:

    短版:子选择中的FOR UPDATE 不是必需的,因为DELETE 实现已经进行了必要的锁定。这将是多余的。

    理想情况下,您应该阅读和消化Concurrency Control,以了解 SQL 引擎如何处理并发问题。

    特别是对于您提到的案例,我认为这两个摘录是最相关的,在Read Committed Isolation Level

    UPDATE、DELETE、SELECT FOR UPDATE 和 SELECT FOR SHARE 命令 在搜索目标行方面的行为与 SELECT 相同:它们 只会找到在命令开始时提交的目标行 时间。

    但是,这样的目标行可能已经更新(或 被另一个并发事务删除或锁定) 成立。在这种情况下,可能的更新程序将等待第一个 更新事务以提交或回滚(如果它仍在 进展)。

    所以你的两个并发 DELETE 之一将等待,只要它试图删除另一个之前已经处理过的行。这种等待只会在另一个提交或回滚时结束。在某种程度上,这意味着引擎“检测到了冲突”并序列化了两个 DELETE 以处理该冲突。

    如果第一个更新程序回滚,那么它的效果是 否定,第二个更新程序可以继续更新 最初发现行。 如果第一个更新者提交,第二个更新者 如果第一个更新者删除了该行,将忽略该行,否则它将 尝试将其操作应用于行的更新版本。

    在您的场景中,在第一个 DELETE 提交并且第二个被唤醒后,第二个将无法删除它等待的行,因为它不再是当前的,它已经消失了。 这不是错误,在此隔离级别中。执行将继续其他行,其中一些也可能已经消失。最终它将报告该语句删除的实际行数,这可能与子选择最初在该语句等待之前找到的行数不同。

    【讨论】:

    • 这里有一个微妙之处。如果您的谓词直接在DELETE 上,那么EvalPlanQual 将在它等待的行锁被释放后重新检查条件。 IIRC 不是子查询的情况,除非您使用 FOR UPDATE,因此您可以更新没有 FOR UPDATE 的行,通过重新检查是否使用 FOR UPDATE 会跳过这些行。
    • 是的。我没有引用关于重新检查条件的文档部分,因为我认为这对提议的场景无关紧要。另一方面,如果提问者对完全可预测/可重现的行为感兴趣,那么答案将是切换到可序列化的隔离级别。
    • @Craig 因此,如果我使用没有 FOR UPDATE 的子查询,那么在收到锁后不会再次检查子查询检索到的行?这不是意味着可以多次删除行吗?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-08-16
    • 2020-05-26
    • 1970-01-01
    • 1970-01-01
    • 2021-11-25
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多