昨日、説明した問題を再現するテスト ケースを作成しました。今日、テストケースに欠陥があることがわかりました。問題が理解できなかったので、昨日の回答は間違っていると思います。
次の 2 つの問題が考えられます。
とのcommit
間にハプニングがあります。update
insert
これは、新しい のみの問題です
AppId
。
テストケース:
テスト テーブルを作成し、2 つの行を挿入します。
session 1 > create table test (TestId number primary key
2 , AppId number not null
3 , Status varchar2(8) not null
4 check (Status in ('inactive', 'active'))
5 );
Table created.
session 1 > insert into test values (1, 123, 'inactive');
1 row created.
session 1 > insert into test values (2, 123, 'active');
1 row created.
session 1 > commit;
Commit complete.
最初のトランザクションを開始します:
session 1 > update test set status = 'inactive'
2 where AppId = 123 and status = 'active';
1 row updated.
session 1 > insert into test values (3, 123, 'active');
1 row created.
2 番目のトランザクションを開始します。
session 2 > update test set status = 'inactive'
2 where AppId = 123 and status = 'active';
セッション 2 はブロックされ、行 2 の行ロックの取得を待機しています。セッション 2 は、セッション 1 のトランザクションがコミットまたはロールバックされるまで続行できません。セッション 1 をコミットします。
session 1 > commit;
Commit complete.
セッション 2 のブロックが解除され、次のように表示されます。
1 row updated.
セッション 2 のブロックが解除されると、更新ステートメントが再開され、セッション 1 の変更が確認され、行3が更新されました。
session 2 > select * from test;
TESTID APPID STATUS
---------- ---------- --------
1 123 inactive
2 123 inactive
3 123 inactive
セッション 2 でトランザクションを完了します。
session 2 > insert into test values (4, 123, 'active');
1 row created.
session 2 > commit;
Commit complete.
結果を確認します (セッション 1 を使用):
セッション 1 > テストから * を選択します。
TESTID APPID STATUS
---------- ---------- --------
1 123 inactive
2 123 inactive
3 123 inactive
4 123 active
update
2 つの が互いにブロックしないようにする唯一の方法は、一方と他方の間でコミットまたはロールバックを行うことです。使用しているソフトウェア スタックのどこかに暗黙のコミットが隠されている可能性があります。私は .NET について十分に理解していないため、.NET を追跡することについてアドバイスすることはできません。
ただし、AppId がテーブルにとって新しい場合、同じ問題が発生します。456 の新しい AppId を使用してテストします。
session 1 > update test set status = 'inactive'
2 where AppId = 456 and status = 'active';
0 rows updated.
行が書き込まれないため、ロックは取得されません。
session 1 > insert into test values (5, 456, 'active');
1 row created.
同じ新しい AppId の 2 番目のトランザクションを開始します。
session 2 > update test set status = 'inactive'
2 where AppId = 456 and status = 'active';
0 rows updated.
セッション 2 は行 5 を認識しないため、行 5 に対するロックを取得しようとはしません。セッション 2 を続行します。
session 2 > insert into test values (6, 456, 'active');
1 row created.
session 2 > commit;
Commit complete.
セッション 1 をコミットして結果を表示します。
session 1 > commit;
Commit complete.
session 1 > select * from test;
TESTID APPID STATUS
---------- ---------- --------
1 123 inactive
2 123 inactive
3 123 inactive
4 123 active
5 456 active
6 456 active
6 rows selected.
修正するには、Patrick Marchand ( Oracle トランザクション分離)の関数ベースのインデックスを使用します。
session 1 > delete from test where AppId = 456;
2 rows deleted.
session 1 > create unique index test_u
2 on test (case when status = 'active' then AppId else null end);
Index created.
新しい AppId の最初のトランザクションを開始します。
session 1 > update test set status = 'inactive'
2 where AppId = 789 and status = 'active';
0 rows updated.
session 1 > insert into test values (7, 789, 'active');
1 row created.
ここでも、セッション 1 は更新でロックを取得しません。行 7 に書き込みロックがあります。2 番目のトランザクションを開始します。
session 2 > update test set status = 'inactive'
2 where AppId = 789 and status = 'active';
0 rows updated.
session 2 > insert into test values (8, 789, 'active');
ここでも、セッション 2 は行 7 を認識しないため、行 7 をロックしようとはしません。しかし、挿入は関数ベースのインデックスの同じスロットに書き込もうとしており、セッション 1 によって保持されている書き込みロックのブロックです。セッション 2 はセッション 1 がcommit
orになるのを待ちますrollback
:
session 1 > commit;
Commit complete.
セッション 2 は次のとおりです。
insert into test values (8, 789, 'active')
*
ERROR at line 1:
ORA-00001: unique constraint (SCOTT.TEST_U) violated
その時点で、クライアントはトランザクション全体を再試行できます。(update
と の両方insert
。)