Oracle MERGE 死锁

Posted

技术标签:

【中文标题】Oracle MERGE 死锁【英文标题】:Oracle MERGE deadlock 【发布时间】:2013-05-16 15:26:10 【问题描述】:

我想用 MERGE 语句以指定的顺序插入行以避免死锁。否则可能会发生死锁,因为多个事务将使用重叠的键集调用此语句。请注意,此代码对重复值异常也很敏感,但我通过重试来处理它,所以这不是我的问题。我正在执行以下操作:

MERGE INTO targetTable
USING (
SELECT ...
FROM sourceCollection
ORDER BY <desiredUpdateOrder>
)
WHEN MATCHED THEN 
UPDATE ...
WHEN NOT MATCHED THEN
INSERT ...

现在我仍然遇到死锁,所以我不确定 oracle 是否维护子查询的顺序。在这种情况下,有谁知道如何最好地确保 oracle 以相同的顺序锁定 targetTable 中的行?我必须在合并之前执行 SELECT FOR UPDATE 吗? SELECT FOR UPDATE 以什么顺序锁定行? Oracle UPDATE 语句有一个 MERGE 似乎缺少的 ORDER BY 子句。除了每次都以相同的顺序锁定行之外,还有其他方法可以避免死锁吗?

[编辑] 此查询用于维护某个操作发生频率的计数。当操作在第一次插入行时发生时,当它发生第二次时,“count”列会增加。有数百万种不同的动作,它们经常发生。表锁不起作用。

【问题讨论】:

您可以尝试通过合并到 SELECT 中来按您的需要对目标表进行排序,例如:MERGE INTO (SELECT * FROM targetTable ORDER BY &lt;desiredUpdateOrder&gt;) USING ...。不确定这是否会解决您的问题,但可能值得一试。分享和享受。 性能是否关键(尤其是响应时间)?如果没有,您是否考虑过序列化代码(在运行合并之前获取应用程序锁,然后在提交时释放)? @JeffreyKemp 应用程序外观不起作用,请参阅编辑。 目标表的维护是否必须与更改同步,或者是否可以延迟?您是否考虑过使用物化视图来存储这些信息? 在这种情况下为 MV 方法 +1 【参考方案1】:

控制修改目标表行的顺序需要你控制USING子查询的查询执行计划。这是一项棘手的工作,并且取决于您的查询可能会获得什么样的执行计划。

如果您遇到死锁,那么我猜您正在获得从源集合到目标表的嵌套循环连接,因为哈希连接可能基于对源集合进行哈希处理并会修改目标表大致按目标表 rowid 顺序排列,因为这将被完全扫描——在任何情况下,访问顺序在所有查询执行中都是一致的。

同样,如果两个数据集之间存在排序合并,您将获得目标表行访问顺序的一致性。

源集合的顺序似乎是可取的,但优化器可能不会应用它,因此请检查执行计划。如果不是,请尝试使用 APPEND 和 ORDER BY 子句将您的数据插入到全局临时表中,然后在没有 order by 子句的情况下从那里进行选择,并探索使用提示来巩固嵌套循环连接。

【讨论】:

【参考方案2】:

我不相信 ORDER BY 会影响任何事情(尽管我非常愿意被证明是错误的);我认为 MERGE 会锁定它需要的一切。

假设我完全错了,假设您使用 MERGE 获得逐行锁。您的问题仍未解决,因为您无法保证您的两个 MERGE 语句不会同时命中同一行。事实上,根据所提供的信息,您无法保证 ORDER BY 会改善情况;这可能会使情况变得更糟。

尽管没有像 UPDATE 那样跳过锁定行的语法,但仍然有一个简单的答案,停止尝试从不同事务中更新同一行。如果可行,您可以使用某种形式的并行执行,例如 DBMS_PARALLEL_EXECUTE 子程序 CREATE_CHUNKS_BY_ROWID 并确保您的事务仅适用于表中行的特定子集。

顺便说一句,您对问题的描述让我有点担心。你说有一些重复的错误可以通过重新运行 MERGE 来修复。如果这些重复项中的数据不同,您需要确保 ORDER BY 不仅针对要合并的数据,还针对要合并的数据into。如果您不这样做,则无法保证您不会用旧的、不正确的数据覆盖正确的数据。

【讨论】:

【参考方案3】:

第一个锁实际上不是在行级别而是在块级别进行管理的。即使不修改同一行,您也可能会遇到 ORA-00060 错误。这可能很棘手。管理这是请求开发人员的工作。

一种可能的解决方法是整理您的桌子(切勿在大桌子或变化率高的桌子上这样做)

https://use-the-index-luke.com/sql/clustering/index-organized-clustered-index

【讨论】:

【参考方案4】:

我建议您尝试锁定行,而不是进行合并。如果成功更新它,如果不插入新行。默认情况下,如果另一个进程对同一事物进行了锁定,则锁定将等待。

CREATE TABLE brianl.deleteme_table
(
    id     INTEGER PRIMARY KEY
  , cnt    INTEGER NOT NULL
);

CREATE OR REPLACE PROCEDURE brianl.deleteme_table_proc (
    p_id   IN deleteme_table.id%TYPE)
    AUTHID DEFINER
AS
    l_id   deleteme_table.id%TYPE;
    -- This isolates this procedure so that it doesn't commit
    -- anything outside of the procedure.
    PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
    -- select the row for update
    -- this will pause if someone already has the row locked.
    SELECT id
      INTO l_id
      FROM deleteme_table
     WHERE id = p_id
    FOR UPDATE;

    -- Row was locked, update it.
    UPDATE deleteme_table
       SET cnt   = cnt + 1
     WHERE id = p_id;

    COMMIT;
EXCEPTION
    WHEN NO_DATA_FOUND
    THEN
        -- we were unable to lock the record, insert a new row
        INSERT INTO deleteme_table (id, cnt)
             VALUES (p_id, 1);

        COMMIT;
END deleteme_table_proc;

CREATE OR REPLACE PROCEDURE brianl.deleteme_proc_test
    AUTHID CURRENT_USER
AS
BEGIN
    -- This resets the table to empty for the test
    EXECUTE IMMEDIATE 'TRUNCATE TABLE brianl.deleteme_table';

    brianl.deleteme_table_proc (p_id => 1);
    brianl.deleteme_table_proc (p_id => 2);
    brianl.deleteme_table_proc (p_id => 3);
    brianl.deleteme_table_proc (p_id => 2);

    FOR eachrec IN (  SELECT id, cnt
                        FROM brianl.deleteme_table
                    ORDER BY id)
    LOOP
        DBMS_OUTPUT.put_line (
            a   => 'id: ' || eachrec.id || ', cnt:' || eachrec.cnt);
    END LOOP;
END;

BEGIN
    -- runs the test;
    brianl.deleteme_proc_test;
END;

【讨论】:

以上是关于Oracle MERGE 死锁的主要内容,如果未能解决你的问题,请参考以下文章

如何杀死oracle死锁进程

oracle 死锁和锁等待的区别,锁等待

关于oracle数据库死锁,请大神进。为啥用java synchronized 关键字解决不了?怎么解决死锁问题?

Oracle死锁

Oracle死锁处理实例

如何杀死oracle死锁进程