15

実行するとしましょう...

SELECT * FROM MY_TABLE FOR UPDATE

...そしてMY_TABLEに複数の行があります。

理論的には、2つの同時トランザクションがこのステートメントを実行しても、行を異なる順序でトラバース(したがってロック)すると、デッドロックが発生する可能性があります。例えば:

  • トランザクション1:行Aをロックします。
  • トランザクション2:行Bをロックします。
  • トランザクション1:行Bとブロックをロックしようとします。
  • トランザクション2:行Aとデッドロックをロックしようとします。

これを解決する方法は、ORDER BYを使用して、行が常に同じ順序でロックされるようにすることです。

ですから、私の質問は、この理論上のデッドロックが実際に発生することはあるのでしょうか。人工的に誘発する方法があることは知っていますが、通常の操作で発生する可能性はありますか?常にORDERBYを使用する必要がありますか、それとも実際には省略しても安全ですか?

私は主にOracleとMySQL/InnoDBの動作に興味がありますが、他のDBMSに関するコメントも役に立ちます。

- - 編集 - -

ロック順序が同じでない場合にOracleでデッドロックを再現する方法は次のとおりです。

テストテーブルを作成し、いくつかのテストデータを入力します...

CREATE TABLE DEADLOCK_TEST (
    ID INT PRIMARY KEY,
    A INT 
);

INSERT INTO DEADLOCK_TEST SELECT LEVEL, 1 FROM DUAL CONNECT BY LEVEL <= 10000;

COMMIT;

... 1つのクライアントセッション(SQL Developerを使用)から、次のブロックを実行します。

DECLARE
    CURSOR CUR IS 
        SELECT * FROM DEADLOCK_TEST
        WHERE ID BETWEEN 1000 AND 2000 
        ORDER BY ID 
        FOR UPDATE;
BEGIN
    WHILE TRUE LOOP
        FOR LOCKED_ROW IN CUR LOOP
            UPDATE DEADLOCK_TEST 
            SET A = -99999999999999999999 
            WHERE CURRENT OF CUR;
        END LOOP;
        ROLLBACK;
    END LOOP;
END;
/

別のクライアントセッション(SQL Developerのインスタンスをもう1つ起動しただけです)から、同じブロックを実行しますが、を使用しDESCますORDER BY。数秒後、次の情報が表示されます。

ORA-00060: deadlock detected while waiting for resource

ところで、ORDER BY(両方のブロックが同一になるように)完全に削除し、...を追加することで、同じ結果が得られる可能性があります。

ALTER SESSION SET OPTIMIZER_INDEX_COST_ADJ = 1;

...1つのブロックの前ですが...

ALTER SESSION SET OPTIMIZER_INDEX_COST_ADJ = 10000;

...他の前に(したがって、Oracleは異なる実行プランを選択し、行を異なる順序でフェッチする可能性があります)。

これは、行がカーソルからフェッチされるときにロックが実際に行われることを示しています(カーソルが開いたときに結果セット全体に対して一度に行われるわけではありません)。

4

2 に答える 2

4

あなたの質問の例は、ロックの順序がアクセス方法に依存することを示しています。このアクセス パスは、クエリの ORDER BY 句によって直接決定されるわけではなく、このアクセス パスに影響を与える可能性のある多くの要因があります。したがって、ORDER BY を追加するだけではデッドロックを防ぐことはできません。これは、2 つの異なるアクセス パスが存在する可能性があるためです。実際、order by を使用してテスト ケースを実行し、セッション パラメータを変更することで、2 つのセッションを同じクエリで ORA-60 に実行させることができました。

関連するセッションに保留中のロックが他にない場合、すべてのセッションで同じ順序で行をロックするとデッドロックを防ぐことができますが、この順序を確実に強制するにはどうすればよいでしょうか? とにかく、これはデッドロックのこの非常に特殊なケースを防ぐだけであることに注意してください。各セッションまたは異なるプランで複数のクエリを使用すると、デッドロックが発生する可能性があります。

実際には、このケースは非常に特殊であり、とにかく頻繁に発生するべきではありません。デッドロックが心配な場合でも、デッドロックを防ぐ簡単な方法があると思います。

デッドロックを防ぐ最も簡単な方法は、またはのいずれかを使用することですFOR UPDATE NOWAIT(FOR UPDATE WAIT Xただし、WAIT X はデッドロック検出メカニズムよりも優れた X の値でデッドロックをトリガーできますが、現在 11g の時点で 3 秒だと思います。 @APCの修正に感謝します)。

言い換えれば、両方のトランザクションが要求する必要があります。これらの行を渡してロックしますが、別のユーザーが既にロックを持っている場合は、無期限に待機するのではなく、エラーを返します。デッドロックを引き起こすのは無期限の待機です。

実際には、実在のユーザーを使用するほとんどのアプリケーションは、別のトランザクションが完了するまでトランザクションを無期限に待機させるよりも、すぐにエラーを受け取りたいと思います。重要でないバッチ ジョブの場合のみ、FOR UPDATEなしで検討します。NOWAIT

于 2012-07-03T13:39:08.823 に答える
2

FOR UPDATE の仕組みを誤解していると思います。カーソルがアクティブになったとき、つまり SELECT が発行された ときにロックを取得します。

したがって、クエリを実行すると、トランザクション 1 はテーブル全体をロックします (WHERE 句を指定していないため)。トランザクション 2 は、選択したレコードのセットに対してトランザクション 1 が DML を発行したかどうかに関係なく、(WAIT 句で指定した内容に応じて) ハングするか失敗します。実際、トランザクション 1 はレコードをフェッチする必要さえありません。トランザクション 1 が FOR UPDATE カーソルを開くと、トランザクション 2 は ORA-00054 をスローします。

あなたが説明するデッドロックのシナリオは、楽観的ロックを使用するアプリケーションの古典的な結果です (つまり、必要なときにロックを取得できると想定しています)。FOR UPDATE の要点は、悲観的なロック戦略であるということです。将来の処理の成功を保証するために、現在必要とされる可能性のあるすべてのロックを取得します。


計り知れない Kyte 氏は、彼のブログで重要な洞察を提供しています。

「デッドロックの検出は待機期間より優先されます」

私のコードでは、2 番目のセッションで使用されるカーソルの FOR UPDATE 句で NOWAIT を使用していました。

cursor c10000 is
     select * from order_lines
     where header_id = 1234
     for update;

cursor c1 is
     select * from order_lines
     where header_id = 1234
     and line_id = 9999
     for update nowait;

その結果、セッション 2 はすぐに失敗し、ORA-00054 がスローされます。

ただし、OP には何も指定されていません。その場合、2 番目のセッションは行が解放されるまで無期限に待機します。そうでないことを除いて、しばらくするとデッドロック検出が開始され、極端な偏見、つまりORA-00060でコマンドが終了するためです。彼らが短い待機期間を指定していた場合 (WAIT 1 など)、彼らは見たでしょうORA-30006: resource busy

これは、詳細な構文を使用するかどうかに関係なく発生することに注意してください...

open c10000;
loop
    fetch c10000 into r; 

またはおしゃれな....

for r in c10000 loop

また、セッション 2 の開始時にセッション 1 が目的の行をフェッチしたかどうかは問題ではありません。

tl;dr

重要なことは、ORDER BY は何も解決しないということです。FOR UPDATE を発行する最初のセッションは、結果セット内のすべてのレコードを取得します。これらのレコードのいずれかを更新しようとする後続のセッションは、NOWAIT、WAIT nを指定したか、何も指定しなかったかに応じて、ORA-00054、ORA-30006、または ORA-00060 で失敗します。 WAIT 期間がタイムアウトするか、デッドロック検出が開始されます。


これが実際の例です。自律型トランザクションを使用して 2 番目のセッションをシミュレートしています。効果は同じですが、出力は読みやすくなっています。

declare
    cursor c1 is
        select * from emp
        where deptno = 10
        for update;
    procedure s2 
    is
        cursor c2 is
            select * from emp
            where empno = 7934 -- one of the employees in dept 10
            for update
            -- for update nowait
            -- for update wait 1
            ;
        x_deadlock exception;
        pragma exception_init( x_deadlock, -60);
        x_row_is_locked exception;
        pragma exception_init( x_row_is_locked, -54);
        x_wait_timeout exception;
        pragma exception_init( x_wait_timeout, -30006);
        pragma autonomous_transaction;
    begin
        dbms_output.put_line('session 2 start');
        for r2 in c2 loop
            dbms_output.put_line('session 2 got '||r2.empno);
            update emp
            set sal = sal * 1.1
            where current of c2;
            dbms_output.put_line('session 2 update='||sql%rowcount);
        end loop;    
        rollback;
     exception
        when x_deadlock then
            dbms_output.put_line('session 2: deadlock exception');
        when x_row_is_locked then
           dbms_output.put_line('session 2: nowait exception');
        when x_wait_timeout then
            dbms_output.put_line('session 2: wait timeout exception');       
    end s2;
begin
    for r1 in c1 loop
        dbms_output.put_line('session 1 got '||r1.empno);
        s2;
    end loop;
end;
/

このバージョンfor updateでは、2 回目のセッションでストレートを指定しました。これは、OP が使用する構成であり、デッドロックが検出されたために出力されたものからわかるように、次のようになります。

session 1 got 7782                                                              
session 2 start                                                                 
session 2: deadlock exception                                                   
session 1 got 7839                                                              
session 2 start                                                                 
session 2: deadlock exception                                                   
session 1 got 7934                                                              
session 2 start                                                                 
session 2: deadlock exception                                                   

PL/SQL procedure successfully completed.

これが明確に示しているのは、

  1. 最初のセッションがまだ結果セットを取得していない場合でも、2 番目のセッションがその 1 つの行をロックしないため、最初のセッションは go-get からの結果セット全体をロックしています。
  2. Deadlock detected2 番目のセッションが何も更新できなかった場合でも、例外がスローされます。1.Deadlock detected最初のセッションでフェッチされた wow が更新されていない場合でも、例外がスローされます。

このコードは、FOR UPDATE バリアントのさまざまな動作を示すために簡単に変更できます。

于 2012-07-03T14:37:01.550 に答える