Code: Select all
rollback;
drop table fruits;
create table fruits
(
bin int primary key,
amount int default (0) not null,
pending int default (0) not null,
cleared date default (sysdate) null
)
/
alter table fruits add (
constraint ck1 check (amount >= 0),
constraint ck2 check (amount + pending >= 0),
constraint ck3 check (
(pending = 0 and cleared is not null)
or (pending <> 0 and cleared is null))
)
/
create or replace trigger FruitsInsUpd before insert or update on fruits
for each row
begin
if :new.pending = 0 and :new.cleared is null or
:new.pending <> 0 and :new.cleared is not null then
raise_application_error (-20101, 'invalid combination of .cleared and .pending');
end if;
end;
/
begin
insert into fruits (bin, amount, pending, cleared) values (1, 10, -2, null);
insert into fruits (bin, amount, pending, cleared) values (2, 0, 2, null);
insert into fruits (bin, amount, pending) values (3, 0, 0);
commit;
end;
/
select * from fruits;
...
BIN AMOUNT PENDING CLEARED
---------- ---------- ---------- ---------
1 10 -2
2 0 2
3 0 0 03-NOV-03
Затем в SQL*Plus сессии 1:
Code: Select all
begin
update fruits set pending = pending - 1, cleared = null where bin = 1;
update fruits set pending = pending + 1, cleared = null where bin = 3;
end;
/
select * from fruits;
...
BIN AMOUNT PENDING CLEARED
---------- ---------- ---------- ---------
1 10 -3
2 0 2
3 0 1
Транзакцию в сессии 1 пока не фиксируем. В сессии 2:
Code: Select all
update fruits
set amount = amount + pending, pending = 0, cleared = sysdate
where cleared is null;
select * from fruits;
Сессия 2 заблокирована, что понятно. Теперь фиксируем транзакцию в сессии 1. Сессия 2 разблокируется, завершает update и выдаёт:
Code: Select all
BIN AMOUNT PENDING CLEARED
---------- ---------- ---------- ---------
1 7 0 03-NOV-03
2 2 0 03-NOV-03
3 0 1
Что, насколько я понимаю, не является результатом, который обещается Oracle - каждый отдельный оператор, включая insert/update/delete, должен увидеть и обработать согласованное состояние базы на момент начала оператора вне зависимости от уровня изоляции. Причём на READ COMMITTED, в отличие от SERIALIZABLE, момент начала отдельного оператора в Oracle подвижен (мы это обсуждали пару месяцев назад здесь на Привете) - при необходимости, это делается откатом и перезапуском оператора незаметно для пользователя. И именно поэтому на READ COMMITTED никогда не бывает видно ошибок сериализации.
В этом примере сессия 2 обновила строки 1 и 2 после того, как завершилась транзакция в сессии 1, а строка 3, как это ни странно, была обработана так, как она была на состояние до завершения транзакции в сессии 1 - т.е. сессия 2 вообще не заметила эту строку, хотя предикат в where должен был её отобрать.
Идея поставить такой опыт у меня появилась после прочтения ответов на asktom.oracle.com (см. write "consistency") на вопросы об их реализации consistent write - судя по описанию Тома как это работает, у Oracle могла бы наблюдаться такая аномалия из-за одной неаккуратно сделанной оптимизации в реализации алгоритма автоматического разрешения конфликтов записи.
Это может быть потеницально очень опасной ошибкой, которая элементарно может приводить к несогласованности данных в приложениях, ожидающих честного write consistency. С удовольствием послушаю критику, прежде чем поделиться находкой с Томом - может я всё-таки что-то упустил?