昨天我创建了一个测试用例来重现所描述的问题。今天发现测试用例有问题。我没看懂问题,所以我相信我昨天给出的答案是不正确的。
有两个可能的问题:
发生了commit
在update 和insert 之间。
这只是新的问题
AppIds.
测试用例:
创建测试表并插入两行:
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
两个updates 不相互阻塞的唯一方法是在一个和另一个之间进行提交或回滚。您正在使用的软件堆栈中的某处可能隐藏着隐式提交。我对 .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 transaction isolation) 的基于函数的索引:
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 或 @987654344 @:
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。)