Не сме сами, част 1
Oct 28th, 2009 by yavor
Първият пример е за система, в която постъпват данни от много места (асинхронно) и се правят се различни обработки (асинхронно). По всяко време някой процес може да извика дадена процедура и понякога това може да предизвика конфликти, които да доведат до дублиране (по-добрия вариант) или загуба на информация.
Ето примерна процедура, която води до дублиране на информация:
-
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.
Едно по-добро решение ще предложа малко по-късно.