Вчера я создал контрольный пример для воспроизведения описанной проблемы.Сегодня я обнаружил, что контрольный пример был ошибочным.Я не понял проблему, поэтому я считаю, что ответ, который я дал вчера, неверен.
Возможны две проблемы:
Существует commit
происходит между update
и insert
.
Это только проблема для новых AppId
с.
Контрольный пример:
Создайте тестовую таблицу и вставьте две строки:
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
Единственный способ для двух update
не блокировать друг друга - это сделать коммит или откат между одним и другим.Может быть неявная фиксация, скрытая где-то в программном стеке, который вы используете.Я не знаю достаточно о .NET, чтобы посоветовать отследить это.
Однако та же проблема произойдет, если AppId является новым для таблицы.Протестируйте с использованием нового AppId 456:
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.
Для исправления используйте индекс на основе функций от Патрика Маршана ( Изоляция транзакции 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
или 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
.)