Oracle: statement-level write consistency question.

User avatar
tengiz
Уже с Приветом
Posts: 4468
Joined: 21 Sep 2000 09:01
Location: Sammamish, WA

Oracle: statement-level write consistency question.

Post by tengiz »

Проверено на Oracle 9i, 8i. Setup:

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. С удовольствием послушаю критику, прежде чем поделиться находкой с Томом - может я всё-таки что-то упустил?
Cheers
vc
Уже с Приветом
Posts: 664
Joined: 05 Jun 2002 01:11

Re: Oracle: statement-level write consistency question.

Post by vc »

tengiz,

Long time, no hear ;)

tengiz wrote: ... <bobbitted>...С удовольствием послушаю критику, прежде чем поделиться находкой с Томом - может я всё-таки что-то упустил?


The crucial point you are missing is that the update/delete statement is restarted only if the row the second session is blocked on is _changed_, or more precisely, the column(s) relevant to the predicate in the 'where' clause are. If the modified values are the same as before, then the row is considered unchanged.

Briefly:
1. the second update scans the rows, comes to a locked row and blocks;
2. the first session commits;
3. the second session checks if the row is changed, and if so, restarts and, thereby, possibly acquires new rows satisfying the predicate;
if the row is unchanged, there is no restart. The same is true if the first session rolls back, naturally.

We can discuss the stuff in more detail tomorrow if you wish.

Rgds.
User avatar
camel
Новичок
Posts: 86
Joined: 06 Dec 2002 18:21

Post by camel »

del
User avatar
tengiz
Уже с Приветом
Posts: 4468
Joined: 21 Sep 2000 09:01
Location: Sammamish, WA

Re: Oracle: statement-level write consistency question.

Post by tengiz »

vc wrote:The crucial point you are missing is that the update/delete statement is restarted only if the row the second session is blocked on is _changed_, or more precisely, the column(s) relevant to the predicate in the 'where' clause are. If the modified values are the same as before, then the row is considered unchanged.

Вот имено это и является проблемой - в пространных объяснениях на asktom мне не понравился как раз этот пункт: проверка неизменности значений колонок, участвующих в предикате отбора. Подобная оптимизация не является корректной. На самом деле нужно убедиться, что не изменилась ни одна колонка из тех, что вообще упоминаются в запросе. В данном примере в строках 1 и 3 изменились amount и pending, поэтому запрос должен был перестартовать, чтобы не было несогласованности при последущем выполнении.

Но запрос не перестартовал, а просто продолжился дальше с новыми значениями колонок, не входящих в предикат. Что, собственно, и привело к неправильнму результату: сессии 2 не было обеспечено согласованной картины - колонки amount и pending в строках 1 и 2 новые, а в строке 3 старые! И полученное в итоге состояние данных не соответствует никакому варианту последовательного выполнения этих транзакций.

Любопытно, кстати, что если бы сессия 2 вместо update выполнила select... for update, то картина была бы иная:

Code: Select all

select amount, pending, cleared from fruits where cleared is null for update;
...
    AMOUNT    PENDING CLEARED
---------- ---------- ---------
        10         -3
         0          2
         0          1

Такой результат уже является совершенно безупречным. И если бы обновление таблицы делалось на этом наборе строк, то я бы не имел никаких вопросов. Но, как мы видим, ничего, что было бы функционально эквивалентно:

Code: Select all

update (select amount, pending, cleared where cleared is null for update)
set amount = amount + pending, pending = 0, cleared = sysdate;

не происходит. В этом и проблема.
Cheers
vc
Уже с Приветом
Posts: 664
Joined: 05 Jun 2002 01:11

Re: Oracle: statement-level write consistency question.

Post by vc »

Hello tengiz,

I apologize for my wrongly assuming you've missed the role the predicate played in your example. It was not clear from your original message.

Here's what I think assuming we are talking about the READ COMMITTED IL:

tengiz wrote:... мне не понравился как раз этот пункт: проверка неизменности значений колонок, участвующих в предикате отбора. Подобная оптимизация не является корректной. На самом деле нужно убедиться, что не изменилась ни одна колонка из тех, что вообще упоминаются в запросе. В данном примере в строках 1 и 3 изменились amount и pending, поэтому запрос должен был перестартовать, чтобы не было несогласованности при последущем выполнении.


There are essentially two possible outcomes for the second session blocked on the row that was updated earlier by Session 1:

1. The predicate truth value did not change. This case is no different from a scenario wherein the row is not locked at all because Session 2 is free to overwrite the values modified by Session 1 immediately after Session 1 commits. Session 2 will be using a consistent set of rows determined by the predicate and will be relying on the read consistent row values pre-dating Session 1's committed values, so the RCIL promise is _not_ broken.

2. The predicate is false. The Session 2 transaction has to roll back, automatically, and re-start from the beginning for obvious reasons. The second transaction has to wait until Session 1 commits and re-scan the new consistent row values generated by Session 1.

tengiz wrote:Но запрос не перестартовал, а просто продолжился дальше с новыми значениями колонок, не входящих в предикат. Что, собственно, и привело к неправильнму результату: сессии 2 не было обеспечено согласованной картины - колонки amount и pending в строках 1 и 2 новые, а в строке 3 старые! И полученное в итоге состояние данных не соответствует никакому варианту последовательного выполнения этих транзакций.
.


I do not think your claim is valid in the context of the RCIL. You would be right, of course, if we were talking about Serializable.
To sum up, in my opinion, Oracle can get away with the optimization because it can satisfy the RCIL requirements. Again, I think we disagree only on whether the RCIL rules are obeyed in Case 1 (in which the predicate remains true after the modification).

Rgds.
User avatar
tengiz
Уже с Приветом
Posts: 4468
Joined: 21 Sep 2000 09:01
Location: Sammamish, WA

Re: Oracle: statement-level write consistency question.

Post by tengiz »

vc wrote:Oracle can get away with the optimization because it can satisfy the RCIL requirements. Again, I think we disagree only on whether the RCIL rules are obeyed in Case 1 (in which the predicate remains true after the modification).

Да, именно с этим я и не согласен. К тому же поведение Oracle не является таким однозначным - если немного модифицировать опыть и попробовать ещё такие варианты, то будет понятно, что имеется в виду:

1. Создадим no-op before триггер для fruits, но явно упоминающий изменяющиеся колонки:

Code: Select all

create or replace trigger fruitsNoOp before update on fruits for 
each row
  begin
    if :old.pending * 0 <> 0 then
      null;
    end if;
    if :old.cleared is not null and :old.cleared is null then
      null;
    end if;
  end;

Это триггер абсолютно ничего не делает, кроме чтения старых значений двух колонок, и без того прочитанных транзакцией. Тем не менее, наличие такого триггера приводит к правильному результату выполнения транзакции. Причём фокус именно в том, чтобы хоть как-нибудь упомянуть изменившиеся колонки, которые не попали в предикат отбора.

2. Добавим в предикат отбора строк логическое условие, не влияющее на результат, но явно упоминающее изменяющиеся колонки:

Code: Select all

update fruits 
set amount = amount + pending, pending = 0, cleared = sysdate
where cleared is null and pending * 0 = 0 and amount * 0 = 0;

То же самое - сессия 2 теперь видит согласованное состояние данных.

3. Вместо изменения строк одиночным оператором update сессия 2 модифицирует их через такой курсор:

Code: Select all

select amount, pending, cleared where cleared is null for update

Ни одна из этих модификаций не усиливает и не ослабляет требования на уровень изоляции. Тем не менее, Oracle почему-то считает, что с этими модификациями более свободные правила игры не годятся. Моя претензия к Oracle здесь в том, что более строгие правила, задействованные для модифицированных условий, как раз и являются правильными: запрос должен перестартовать если любая из колонок хоть как-то упомянутая в запросе изменилась.
Cheers
vc
Уже с Приветом
Posts: 664
Joined: 05 Jun 2002 01:11

Re: Oracle: statement-level write consistency question.

Post by vc »

tengiz wrote:
vc wrote:Oracle can get away with the optimization because it can satisfy the RCIL requirements. Again, I think we disagree only on whether the RCIL rules are obeyed in Case 1 (in which the predicate remains true after the modification).

Да, именно с этим я и не согласен.


I understand that you disagree but I am not sure why.

1. Session 2's behaviour would've been the same if Session 1 had not existed at all. Exactly the same rows would have been chosen and updated since the predicate would have been the same. Why is it incorrect ? Sure, the transaction schedule is not serializable but nobody promised that, the IL was RC, right ?

2. Of course, a re-scan would've been nice, forgetting for a second about performance, but the behaviour described in (1) is good enough.

tengiz wrote: К тому же поведение Oracle не является таким однозначным - если немного модифицировать опыть и попробовать ещё такие варианты, то будет понятно, что имеется в виду:

1. Создадим no-op before триггер для fruits, но явно упоминающий изменяющиеся колонки:

Code: Select all

create or replace trigger fruitsNoOp before update on fruits for 
each row
  begin
    if :old.pending * 0 <> 0 then
      null;
    end if;
    if :old.cleared is not null and :old.cleared is null then
      null;
    end if;
  end;

Это триггер абсолютно ничего не делает, кроме чтения старых значений двух колонок, и без того прочитанных транзакцией. Тем не менее, наличие такого триггера приводит к правильному результату выполнения транзакции. Причём фокус именно в том, чтобы хоть как-нибудь упомянуть изменившиеся колонки, которые не попали в предикат отбора.

2. Добавим в предикат отбора строк логическое условие, не влияющее на результат, но явно упоминающее изменяющиеся колонки:

Code: Select all

update fruits 
set amount = amount + pending, pending = 0, cleared = sysdate
where cleared is null and pending * 0 = 0 and amount * 0 = 0;

То же самое - сессия 2 теперь видит согласованное состояние данных.

3. Вместо изменения строк одиночным оператором update сессия 2 модифицирует их через такой курсор:

Code: Select all

select amount, pending, cleared where cleared is null for update

Ни одна из этих модификаций не усиливает и не ослабляет требования на уровень изоляции. Тем не менее, Oracle почему-то считает, что с этими модификациями более свободные правила игры не годятся. Моя претензия к Oracle здесь в том, что более строгие правила, задействованные для модифицированных условий, как раз и являются правильными: запрос должен перестартовать если любая из колонок хоть как-то упомянутая в запросе изменилась.


Apparently the behaviour you're describing is a result of Oracle's checking predicate truth value for the locked rows.

Again, please explain why you keep insisting on serializable behaviour in the mode which ain't ? I am genuinely curious.

Rgds.
vc
Уже с Приветом
Posts: 664
Joined: 05 Jun 2002 01:11

Re: Oracle: statement-level write consistency question.

Post by vc »

...A follow-up...

tengiz wrote:
3. Вместо изменения строк одиночным оператором update сессия 2 модифицирует их через такой курсор:

Code: Select all

select amount, pending, cleared where cleared is null for update

Ни одна из этих модификаций не усиливает и не ослабляет требования на уровень изоляции. Тем не менее, Oracle почему-то считает, что с этими модификациями более свободные правила игры не годятся. Моя претензия к Oracle здесь в том, что более строгие правила, задействованные для модифицированных условий, как раз и являются правильными: запрос должен перестартовать если любая из колонок хоть как-то упомянутая в запросе изменилась.


You do realize that there is nothing special about the 'for update' statement ? You'd've gotten the same result, thanks to the RCIL, with an ordinary select if you'd issued it _after_ the commit in Session 1. The 'for update' simply blocks until Session 1 commits, that's all.

I can take a look at the rest later...

Rgds.
User avatar
tengiz
Уже с Приветом
Posts: 4468
Joined: 21 Sep 2000 09:01
Location: Sammamish, WA

Post by tengiz »

vc, в поведении Oracle есть две проблемы:

1. Почему решение о перезапуске принимается только на основании значений колонок, входящих в предикат отбора? Да, я понимаю, что это RC, но заметьте - я и не жду сериализованной транзакции. Я жду сериализованного выполнения одиночного оператора - ведь мне обещается statement-level consistency, что для нашего примера явно неверно.

2. Даже если пожертвовав statement-level consistency согласиться с аргументом о производительности, и согласиться с тем, что единственное зачем нужно следить - это удовлетворяет ли предикату обновлённое значение колонки, то почему Oracle, тем не менее, даже этого не делает? Oracle перезапустит оператор при любом изменении колонки, входящей в предикат. См. пример в моём сообщении выше: значение логического выражения (pending * 0 = 0) всегда true и вообще ни от чего не зависит (pending никогда не бывает NULL). Тем не менее, новое значение этой колонки приводит к перезапуску. В чём смысл?
Cheers
vc
Уже с Приветом
Posts: 664
Joined: 05 Jun 2002 01:11

Post by vc »

tengiz wrote:vc, в поведении Oracle есть две проблемы:

1. Почему решение о перезапуске принимается только на основании значений колонок, входящих в предикат отбора? Да, я понимаю, что это RC, но заметьте - я и не жду сериализованной транзакции. Я жду сериализованного выполнения одиночного оператора - ведь мне обещается statement-level consistency, что для нашего примера явно неверно.


OK, we need some simple definition to avoid talking about different things.
Let's consider an update like this:

update t set x=y <where predicate>, which is conceptually executed as:
update (select x from t where <predicate>) set x=y;

The inner select subquery gets a 'consistent' set of rows as of some moment in time which is different for obvious reasons in cases (a) and (b), see below. Then the selected set of rows is updated/deleted. That's what 'consistent', as applied to an update/delete means, nothing more. This word does not imply any additional benefits, constraints, or some such.

My understanding is that, in this narrow sense, the promise of statement-level consistency is held:
a) the predicate value is unchanged; a consistent set of rows satisfying the predicate is selected as of the moment the second session update was issued;
b) if the predicate value for the locked row is changed, Oracle has to adjust the moment the data is considered consistent, namely to the time when the second session commits. This adjustment (and a re-scan) may need to be done several times if a long running update is blocked, randomly, by several concurrent sessions.


tengiz wrote:2. Даже если пожертвовав statement-level consistency согласиться с аргументом о производительности, и согласиться с тем, что единственное зачем нужно следить - это удовлетворяет ли предикату обновлённое значение колонки, то почему Oracle, тем не менее, даже этого не делает? Oracle перезапустит оператор при любом изменении колонки, входящей в предикат. См. пример в моём сообщении выше: значение логического выражения (pending * 0 = 0) всегда true и вообще не от чего не зависит (pending никогда не бывает NULL). Тем не менее, новое значение этой колонки приводит к перезапуску. В чём смысл?


I'll take a look at this one later.

Rgds.
User avatar
tengiz
Уже с Приветом
Posts: 4468
Joined: 21 Sep 2000 09:01
Location: Sammamish, WA

Post by tengiz »

vc wrote:...The inner select subquery gets a 'consistent' set of rows as of some moment in time... Then the selected set of rows is updated/deleted. That's what 'consistent', as applied to an update/delete means, nothing more. This word does not imply any additional benefits, constraints, or some such.

Session 1:

Code: Select all

drop table a;
create table a (data1 int, data2 int);

begin
insert into a values (1, 1);
insert into a values (0, 0);
end;
/
select * from a;
commit;
update a set data2 = 2;
update a set data1 = 1 where data1 = 0;
...
     DATA1      DATA2
---------- ----------
         1          1
         0          0

Session 2:

Code: Select all

update a set data1 = data2 where data1 <> 0;
select * from a;
commit;

Session 1:

Code: Select all

select * from a;
commit;
...
     DATA1      DATA2
---------- ----------
         1          2
         1          2

Session 2:

Code: Select all

...
     DATA1      DATA2
---------- ----------
         2          2
         1          2

Наборы строк, которые вернулись из сессии 1 после первого и перед вторым commit в этой сессии - это и есть согласованные наборы строк. Никакие другие состояния таблицы <a> никогда не существовали в зафискированном виде, соответственно никакие другие транзакции не могли увидеть в качестве результата одиночного оператора что-либо другое вне зависимости от уровня изоляции. Именно в этом общепринятый смысл согласованного набора. Теперь попробуйте объяснить, каким образом видя один из этих наборов строк и ничего другого, сессия 2 могла произвести такой странный результат?
Cheers
vc
Уже с Приветом
Posts: 664
Joined: 05 Jun 2002 01:11

Post by vc »

tengiz wrote:...
2. Даже если пожертвовав statement-level consistency согласиться с аргументом о производительности, и согласиться с тем, что единственное зачем нужно следить - это удовлетворяет ли предикату обновлённое значение колонки, то почему Oracle, тем не менее, даже этого не делает? Oracle перезапустит оператор при любом изменении колонки, входящей в предикат. См. пример в моём сообщении выше: значение логического выражения (pending * 0 = 0) всегда true и вообще ни от чего не зависит (pending никогда не бывает NULL). Тем не менее, новое значение этой колонки приводит к перезапуску. В чём смысл?


I simplified your code a little by removing triggers and constraints:

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
)
/


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;

Session 1:

update fruits set pending = pending - 1 , cleared = null where bin = 1;
update fruits set pending = pending + 1, cleared = null where bin = 3;

Session 2:

update fruits
set cleared = sysdate
where cleared is null and pending * 0 = 0;
...blocks ..

Session 1:
commit;

Session 2:

3 rows updated.

select * from fruits;

       BIN     AMOUNT    PENDING CLEARED
---------- ---------- ---------- ---------
         1         10         -3 04-NOV-03
         2          0          2 04-NOV-03
         3          0          1 04-NOV-03



Oracle behaves quite predictably:

1. Session 1 locks rows 1 and 3;
2. Session 2 blocks on row 1;
3. Session 1 commits;
4. Session 2 checks the 'cleared' column... unchanged..., checks 'pending' ...*changed*, a re-scan follows and discovers a new row satisfying the predicate (row 3).

If you replace 'update fruits set pending = pending - 1 , cleared = null where bin = 1; ' with 'update fruits set pending = pending , cleared = null where bin = 1; ', then there will be no re-scan in Step 4 because no predicate column changed.

I agree that Oracle probably should not re-scan even if the column changed because it's defined as 'not null'.

Also, please remember that a re-scan may happen only once per intervening transaction, for the first row the update transaction blocked on and only a) iff the row column changed and b) participates in the predicate evaluation. The re-scan does not occur for each changed row.

Rgds.
.
vc
Уже с Приветом
Posts: 664
Joined: 05 Jun 2002 01:11

Post by vc »

tengiz wrote:
vc wrote:...The inner select subquery gets a 'consistent' set of rows as of some moment in time... Then the selected set of rows is updated/deleted. That's what 'consistent', as applied to an update/delete means, nothing more. This word does not imply any additional benefits, constraints, or some such.

Session 1:

Code: Select all

drop table a;
create table a (data1 int, data2 int);

begin
insert into a values (1, 1);
insert into a values (0, 0);
end;
/
select * from a;
commit;
update a set data2 = 2;
update a set data1 = 1 where data1 = 0;
...
     DATA1      DATA2
---------- ----------
         1          1
         0          0

Session 2:

Code: Select all

update a set data1 = data2 where data1 <> 0;
select * from a;
commit;

Session 1:

Code: Select all

select * from a;
commit;
...
     DATA1      DATA2
---------- ----------
         1          2
         1          2

Session 2:

Code: Select all

...
     DATA1      DATA2
---------- ----------
         2          2
         1          2

Наборы строк, которые вернулись из сессии 1 после первого и перед вторым commit в этой сессии - это и есть согласованные наборы строк. Никакие другие состояния таблицы <a> никогда не существовали в зафискированном виде, соответственно никакие другие транзакции не могли увидеть в качестве результата одиночного оператора что-либо другое вне зависимости от уровня изоляции. Именно в этом общепринятый смысл согласованного набора. Теперь попробуйте объяснить, каким образом видя один из этих наборов строк и ничего другого, сессия 2 могла произвести такой странный результат?


The same old story with predicates and columns:

.........
Session 1 after the update:

r1(1,2)
r2(1,2)

Session 2 sees the committed data:
r1(1,1)
r2(0,0)

... tries to update r1 because data1 <>0 = true; r2 does not satisfy the predicate.
... blocks on r1

Session 1 commits;

Session 2 is unblocked, the predicate column did not change so no re-scan is needed, only one row satisfies the '<>' predicate as before.
Session 2 uses the new data committed by Session 1:
r1(1,2)
... and performs the update:
r1(2,2)

r2(1,2) is not updated since it's not in the set defined by the '<>' predicate. Remember, the 'select' part of the update scanned consistent data as of the point in time _before_ Session 1 committed, right ?

Another useful point to remember is that data is always located in the consistent mode but updated in the current mode, as of 'right now'.

That's all there is to it, really.

Rgds.
User avatar
tengiz
Уже с Приветом
Posts: 4468
Joined: 21 Sep 2000 09:01
Location: Sammamish, WA

Post by tengiz »

vc, возможно я, как всегда, нечётко выражаюсь. Попробую слегка по-другому:

Я прекрасно понимаю, что делает Oracle. Ваши объяснения как это происходит не вызывают у меня почти никаких возражений. Проблема в том, что я считаю, что то что делает Oracle: во-первых, неправильно; во-вторых, не соответствует тому, что обещано в документации.

Почему я считаю, что результат, полученный в этих опытах неверный? Потому что транзакция атомарна по определению. Никакие других транзакции не должны видеть половину зафиксированного состояния (случай грязного чтения, очевидно, игнорируем). Или всё, или ничего. А Oracle на RC этого не обеспечивает в рассматриваемом примере не то что для транзакции, а даже для отдельного оператора, который тоже атомарен по определению - чего требует и стандарт, и формальная теория, и просто здравый смысл. Но оператор, тем не менее, отработал так, как если бы ему была представлена только часть зафиксированных данных.

Причина - алгоритм, который применяет Oracle для разруливания конфликтующих обновлений, для оптимизации производительности с целью избежать ненужных (на взгляд Oracle) перезапусков, на мой взгляд имеет серьёзный дефект, который приводит к искажению данных. Круг замкнулся - именно с этого я и начинал эту дискуссию.

Попробуйте, кстати, проделать такой же опыт на SQL Server или Sybase.
Last edited by tengiz on 05 Nov 2003 08:15, edited 1 time in total.
Cheers
User avatar
tengiz
Уже с Приветом
Posts: 4468
Joined: 21 Sep 2000 09:01
Location: Sammamish, WA

Post by tengiz »

Here's another one - what if there is no predicate at all? Do you believe that in this case Oracle should never re-scan? If so, then how would you like the following scenario. Session 1:

Code: Select all

drop table a;
create table a
(
   pk int primary key,
   fk int null,
   data int null,
   constraint c_fk foreign key (fk) references a (pk)
);
insert into a values (1, null, null);
commit;
select * from a;

insert into a values (2, 1, null);
update a set fk = 2 where pk = 1;
select * from a;
...
        PK         FK       DATA
---------- ---------- ----------
         1
...
        PK         FK       DATA
---------- ---------- ----------
         1          2
         2          1
Session 2:

Code: Select all

update a set data = fk;
select * from a;
commit;
Session 1:

Code: Select all

commit;
Session 2:

Code: Select all

        PK         FK       DATA
---------- ---------- ----------
         1          2          2 <-- looks like we did see the latest committed state.
         2          1            <-- nope, not true for this one

Do you believe that the result produced by session 2 is consistent? If so, then I rest my case.

Now, if in the session 2 instead of <update a set data = fk> you'd issue <update a set pk = pk, data = fk>, the result would be different:

Code: Select all

        PK         FK       DATA
---------- ---------- ----------
         1          2          2
         2          1          1

And this is consistent indeed.
Cheers
J.K.
Уже с Приветом
Posts: 711
Joined: 18 Jun 2003 06:58
Location: su/us

Re: Oracle: statement-level write consistency question.

Post by J.K. »

tengiz wrote:Проверено на Oracle 9i, 8i. Setup:

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. С удовольствием послушаю критику, прежде чем поделиться находкой с Томом - может я всё-таки что-то упустил?


На момент начала 2-й транзакции мы работаем с ролбек сегментом. Посмотрим что там?

select * from fruits;

BIN AMOUNT PENDING CLEARED
---------- ---------- ---------- ---------
1 10 -2
2 0 2
3 0 0 04-NOV-03

Теперь мы говорим
update fruits
set amount = amount + pending, pending = 0, cleared = sysdate
where cleared is null;

Естественно для 2-й транзакции мы хотим обновить 1 и 2ю запись, но никак не 3-ю. Правильно?

что и получаем.
BIN AMOUNT PENDING CLEARED
---------- ---------- ---------- ---------
1 7 0 03-NOV-03
2 2 0 03-NOV-03
3 0 1

если следовать логике первой транзакции, во второй транзакции мы должны поступить так:

update fruits
set amount = amount + pending, pending = 0, cleared = sysdate
where bin=3;

Отпускаем первую транзакцию

Смотрим вторую.

SQL> select * from fruits;

BIN AMOUNT PENDING CLEARED
---------- ---------- ---------- ---------
1 10 -3
2 0 2
3 1 0 04-NOV-03

То что нужно? На мой взглыд да.

Ни для кого не секрет как доказываются теоремы что прямой угол равен острому.

Наперстки....Следите за пальцами.

Так как работают остальные DB ? Кто кривой ?
User avatar
tengiz
Уже с Приветом
Posts: 4468
Joined: 21 Sep 2000 09:01
Location: Sammamish, WA

Re: Oracle: statement-level write consistency question.

Post by tengiz »

J.K., правильный ответ:

Code: Select all

       BIN     AMOUNT    PENDING CLEARED
---------- ---------- ---------- ---------
         1          7          0 04-NOV-03
         2          2          0 04-NOV-03
         3          1          0 04-NOV-03

В противном случае изоляция транзакций - это фикция.
Cheers
User avatar
Dmitry67
Уже с Приветом
Posts: 28294
Joined: 29 Aug 2000 09:01
Location: SPB --> Gloucester, MA, US --> SPB --> Paris

Post by Dmitry67 »

Ох, как не хочется людям признавать очевидное ! :)
Зарегистрированный нацпредатель, удостоверение N 19719876044787 от 22.09.2014
User avatar
Dmitry67
Уже с Приветом
Posts: 28294
Joined: 29 Aug 2000 09:01
Location: SPB --> Gloucester, MA, US --> SPB --> Paris

Re: Oracle: statement-level write consistency question.

Post by Dmitry67 »

tengiz wrote:С удовольствием послушаю критику, прежде чем поделиться находкой с Томом - может я всё-таки что-то упустил?


Еще не постили ?

P.S. Интересно, а если у Вас есть лицензия Oracle, что если обратиться в support и поиметь терпение преодолеть первые бастионы supportа для идиотов ?
Зарегистрированный нацпредатель, удостоверение N 19719876044787 от 22.09.2014
User avatar
tengiz
Уже с Приветом
Posts: 4468
Joined: 21 Sep 2000 09:01
Location: Sammamish, WA

Re: Oracle: statement-level write consistency question.

Post by tengiz »

Dmitry67 wrote:Еще не постили ?

P.S. Интересно, а если у Вас есть лицензия Oracle, что если обратиться в support и поиметь терпение преодолеть первые бастионы supportа для идиотов ?

Нет, ещё не постил. У него там backlog, поэтому он новые вопросы пока не принимает. Но чтобы время зря не терять, можно здесь на Привете обкатать и побить об углы простую и понятную аргументацию.

Лицензии у меня нет - я просто скачал обе версии оракла с сайта производителся и балуюсь у себя на домашней машине. Да и потом саппорт - это, боюсь, не то место, где можно поговорить об индукции с дедукцией. У них - продукция.
Cheers
J.K.
Уже с Приветом
Posts: 711
Joined: 18 Jun 2003 06:58
Location: su/us

Re: Oracle: statement-level write consistency question.

Post by J.K. »

tengiz wrote:J.K., правильный ответ:

Code: Select all

       BIN     AMOUNT    PENDING CLEARED
---------- ---------- ---------- ---------
         1          7          0 04-NOV-03
         2          2          0 04-NOV-03
         3          1          0 04-NOV-03

В противном случае изоляция транзакций - это фикция.


Sorry for translit.

Izolation Row or Table ?

But, 2-a transaction zablokirovala 1 & 2 record v ozhidanii 1-y transaction, na 3-y record 2-y transaction _nachixat'_ na dannay moment. I eto pravil'no. Nachalo 1-y & 2-y transaction proizoshlo practicheski odnovremenno. 2-y transaction dolzhna obnovit' 1 & 2 record!!!, but not 3-y.
A ti xochesh skazat' chto nachalo 2-y transaction dolzhno nachinat'sya posle okonchania 1-y - eto nepravil'no.

Xotya, Tengiz, ya tvoy point ponimayu, no ne schitayu, chto eto verno. Eto ne Bug, eto philosophy.
User avatar
Dmitry67
Уже с Приветом
Posts: 28294
Joined: 29 Aug 2000 09:01
Location: SPB --> Gloucester, MA, US --> SPB --> Paris

Re: Oracle: statement-level write consistency question.

Post by Dmitry67 »

tengiz wrote:Да и потом саппорт - это, боюсь, не то место, где можно поговорить об индукции с дедукцией. У них - продукция.


Тенгиз, пять баллов за фразу !
Зарегистрированный нацпредатель, удостоверение N 19719876044787 от 22.09.2014
zVlad
Уже с Приветом
Posts: 15311
Joined: 30 Apr 2003 16:43

Post by zVlad »

Поведение Оракл в самом первом сценарии Tengiza может быть интерпретированно следующим образом:
На начальном этапе выполнения - процессировании WHERE clause - Update второй сессии (впрочем любой update) работает как SELECT в read-only версионнике и намечает строки для процессирования.
В выбранный набор попадают 1 и 2 строки. Третья строка в этот набор не попадает, потому что ее данные до начала первой, незакоммиченной сесии, не удовлетворяют критерию второго UPDATE.
На втором этапе - собственно изменение - второй UPDATE обнаруживает выбранную первую строку заблокированной и переходит в ожидание переодически просыпаясь и проверяя - не освободилась ли нужная ему строка. Проснувшись в очередной раз второй UPDATE находит строки 1 и 2 не залокированными и модифицирует их.

В рамках приведенного выше объяснения, иначе говоря с точки зрения версионного подхода к решению проблем concurrency, а точнее Оракловского понимания версионного подхода, результат полученный в эксперименте - правильный результат. Что и отстаивают представители команды "Оракл".

С точки же зрения блокировочного (изоляционного) подхода результат не верен, потому что согласно этому подходу второй UPDATE должен был ждать пока первый не снимет замок со строк 1 и 3 и только затем проверять соответствуют ли эти строки критерию в WHERE clause.

Мне так кажется, что в Оракл для процессирования where используется один и тот же код во всех случаях. И этот код использует UNDO сегмент. Кое-какие явные недостатки такого обобщения устранены через ре-старт, который работает не для всех случаев, что и позволяет иметь накладки приведенные в обсуждаемом сценарии.

Вот если Оракл удалит из своей документации ссылки на уровни изоляции, то тогда к Ораклу вообще никаких притензий не будет. Можно будет просто говорить: есть базы данных, обеспечивающие конкуренцию через изоляцию, и Оракл.
User avatar
Dmitry67
Уже с Приветом
Posts: 28294
Joined: 29 Aug 2000 09:01
Location: SPB --> Gloucester, MA, US --> SPB --> Paris

Post by Dmitry67 »

Мне кажется что результат может быть верным или нет
Какой подход использован для его получения вообще не важно
При оценке корректности результата базу надо рассматривать вообще как черный ящик. Непонятно, как объяснение деталей имплементации и рассуждения о замках может заставить поверить что неправильный результат - правильный.

tengiz, а можете придмать на основе этого более практический пример, ну например что нибудь вроде есть банк, в банке со счета на счет трензакции перебрасывают деньги. Общая сумма при этом остается неизменной. Если бы удалось построить кверь нарушающую некий инвариант (сумма денег на всех счетах константа) то вот это и было бы стопроцентно показательным неправильным результатом, против которого уже не попрешь
Зарегистрированный нацпредатель, удостоверение N 19719876044787 от 22.09.2014
User avatar
camel
Новичок
Posts: 86
Joined: 06 Dec 2002 18:21

Post by camel »

Мое представление, возможно несколько упрощенное, того, как работает UPDATE в оракле.

Команда UPDATE выполняется в два этапа - поиск строк и их модификация.

Первый этап - поиск строк выполняется сразу же в момент подачи команды UPDATE, скажем в момент t1. Поиск строк для обновления, соответствующих критерию WHERE, выполняется точно так же, как это происходит в SELECT, т.е. для строк, заблокированных другими незавершенными транзакциями, используются данные из сегментов отката.

Если найденные строки - кандидаты на обновление в момент t1 заблокированы другой незавершенной транзакцией, второй этап выполнения UPDATE откладывается до снятия этих блокировок, т.е. до момента t2.

После снятия блокировок от других транзакций, в момент t2, выполняется второй этап UPDATE - модификация текущих в этот момент версий строк-кандидатов, причем проверяется, чтобы критерий WHERE все еще выполнялся, иначе строка обновлена не будет.

В некоторых случаях в момент t2 оракл выполняет повторный поиск строк для обновления, что на первый взгляд запутывает картину, но по-существу ничего не меняет, потому что это просто переносит момент начала всей команды UPDATE с t1 на t2.

Основным моментом, вызывающим дискуссию, по-видимому является то, что поиск строк выполняется в момент t1, когда изменения от других транзакций еще не зафиксированы и поэтому не видны, а обновление строк происходит в момент t2, когда эти изменения уже видны. Естественно, что строки уже не те, которые были в момент их поиска. Это может привести к проблемам типа "lost update", что логически вполне понятно. Там, где этот риск неприемлем, следует использовать SELECT FOR UPDATE или SERIALIZABLE.

Return to “Вопросы и новости IT”