昨天我创建了一个测试用例来重现所描述的问题。今天发现测试用例有问题。我不明白这个问题,因此,我相信我昨天给出的答案是不正确的。
有两个可能的问题:
和commit
之间发生了一件事情。update
insert
这只是 new
AppId
s 的问题。
测试用例:
创建测试表并插入两行:
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.
开始第二笔交易:
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
两个 s 不相互阻塞的唯一方法update
是在一个和另一个之间进行提交或回滚。您正在使用的软件堆栈中的某处可能隐藏着隐式提交。我对 .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 启动第二个事务:
session 2 > update test set status = 'inactive'
2 where AppId = 456 and status = 'active';
0 rows updated.
会话 2 看不到第 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 行有一个写锁。开始第二个事务:
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 行,因此它不会尝试对其进行锁定。但是插入尝试写入基于函数的索引上的同一插槽,并阻塞会话 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
。)