Окт. 282009
 

(към част 0)

Първият пример е за система, в която постъпват данни от много места (асинхронно) и се правят се различни обработки (асинхронно). По всяко време някой процес може да извика дадена процедура и понякога това може да предизвика конфликти, които да доведат до дублиране (по-добрия вариант) или загуба на информация.

Ето примерна процедура, която води до дублиране на информация:

create or replace procedure Move_Some_Data is
  -- example code - has a BUG!!!
begin
  -- find all non-archived batches
  for batches in (select batch_id
                    from batch_log
                   where archived = 0) loop
    -- archive the current batch
    insert /*+append*/
    into arch_table
      select *
        from prod_table
       where batch_id = batches.batch_id;
  
    -- delete the rows from prod table
    delete from prod_table
     where batch_id = batches.batch_id;

  ... -- other tables containing batch information
  
    -- mark the batch as archived
    update batch_log
       set archived = 1
     where batch_id = batches.batch_id;
  
    -- finish the transaction
    commit;
  end loop;
end;

В този случай, ако по някаква грешка пуснем процедурата 2 пъти (почти) едновременно, има голяма шанс едни и същи данни да се случат в архива 2 пъти. Ето един интересен вариант:

– Сесия 1 изпълнява select заявката и получава курсор към не-архивираните batches 89, 90, 91 и 92. Започва цикъл за прехвърляне.
– Сесия 1 минава една итерация и прехвърля batch 89. Започва insert – select от втора итерация за batch 90
– Сесия 2 се включва в този момент. Изпълнява бързия select и получава курсор към не-архивираните (към този момент) batches 90, 91 и 92. Започва цикъл за прехвърляне.
– Сесия 2 също започва insert – select за прехвърляне на batch 90
– Сесия 1 завършва прехвърлянето на batch 90 и изтрива данните. Това не пречи по никакъв начин на сесия 2, защото тя получава consistent read копие на данните от преди изтриването
– Нататък може да стават всякакви произволни комбинации. Примерно ако данните в batch 91 са значително по-малко от тези в batch 90, сесия 1 може да ги прехвърли и изтрие преди сесия 2 да е свършила с първата си итерация. В този случай когато Сесия 2 стигне до втората си итерация, ще направи insert на 0 реда (и съответно delete), което би било много забавно за наблюдение ако има организиран log.

Елементарната защита в такъв случай би била вместо select да правим select for update на таблица batch_log. Това, в случая, не върши работа, защото (съвсем коректно), след всяка прехвърлена група записи се прави commit.

Едно по-добро решение ще предложа малко по-късно.

(към част 2)

 Posted by at 9:07

Sorry, the comment form is closed at this time.