4

我想以指定的顺序插入带有 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”列会增加。有数百万种不同的动作,它们经常发生。表锁不起作用。

4

4 回答 4

3

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

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

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

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

于 2013-05-17T06:59:33.800 回答
1

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

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

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

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

于 2013-05-16T15:36:44.813 回答
0

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

一种可能的解决方法是整理你的桌子(永远不要在巨大的桌子或变化率很高的桌子上这样做)

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

于 2022-01-25T20:21:43.760 回答
0

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

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;
于 2022-01-26T01:47:10.643 回答