2013-02-27 10 views
5

Mam bazę danych Oracle, do której uzyskuję dostęp za pomocą Devart i Entity Framework.Równoczesne odczytywanie i aktualizowanie w tabeli bazy danych

Istnieje tabela o nazwie IMPORTJOBS z kolumną STATUS.

Mam również wiele procesów uruchomionych w tym samym czasie. Każdy z nich przeczytał pierwszy wiersz w IMPORTJOBS, który ma status 'REGISTERED', umieścił go w stanie 'EXECUTING', a jeśli zostanie zrobiony, ustaw go na status 'EXECUTED'.

teraz, ponieważ te procesy są uruchomione równolegle, wierzę następujących może się zdarzyć:

  • sposób A czyta wiersz 10, który ma status REGISTERED,
  • proces B odczytuje także wiersz 10, który ma jeszcze statusu REGISTERED ,
  • proces A aktualizuje wiersz 10 do statusu EXECUTING.

Proces B nie powinien być w stanie odczytać wiersz 10 jako sposobu A już czytać i będzie aktualizować swój status.

Jak rozwiązać ten problem? Czy chcesz przeczytać i zaktualizować transakcję? Czy powinienem użyć jakiegoś podejścia do wersjonowania lub czegoś innego?

Dzięki!

EDYCJA: dzięki przyjętej odpowiedzi Mam to działa i udokumentowałem tutaj: http://ludwigstuyck.wordpress.com/2013/02/28/concurrent-reading-and-writing-in-an-oracle-database.

Odpowiedz

2

Należy użyć wbudowanych mechanizmów blokujących bazy danych. Nie wynajduj ponownie koła, zwłaszcza, że ​​RDBMS to zaprojektowane, aby radzić sobie ze współbieżnością i spójnością.

W Oracle 11g, proponuję użyć funkcji SKIP LOCKED. Na przykład każdy proces może wywołać funkcję tak (zakładając id to liczba):

CREATE OR REPLACE TYPE tab_number IS TABLE OF NUMBER; 

CREATE OR REPLACE FUNCTION reserve_jobs RETURN tab_number IS 
    CURSOR c IS 
     SELECT id FROM IMPORTJOBS WHERE STATUS = 'REGISTERED' 
     FOR UPDATE SKIP LOCKED; 
    l_result tab_number := tab_number(); 
    l_id number; 
BEGIN 
    OPEN c; 
    FOR i IN 1..10 LOOP 
     FETCH c INTO l_id; 
     EXIT WHEN c%NOTFOUND; 
     l_result.extend; 
     l_result(l_result.size) := l_id; 
    END LOOP; 
    CLOSE c; 
    RETURN l_result; 
END; 

powoduje przywrócenie 10 rzędów (jeśli to możliwe), które nie są zablokowane. Te wiersze zostaną zablokowane, a sesje nie będą się blokować.

W 10g i wcześniej, ponieważ Oracle zwraca spójne wyniki, użyj FOR UPDATE i nie powinieneś mieć problemu, który opisujesz. Na przykład należy rozważyć następujące SELECT:

SELECT * 
    FROM IMPORTJOBS 
WHERE STATUS = 'REGISTERED' 
    AND rownum <= 10 
FOR UPDATE; 

Co by się stało, gdyby wszystkie procesy zarezerwować swoje wiersze z tego SELECT? W jaki sposób wpłynie to na Twój scenariusz:

  1. Sesja A otrzymuje 10 wierszy, które nie są przetwarzane.
  2. Sesja B otrzyma te same 10 wierszy, zostanie zablokowana i czeka na sesję A.
  3. Sesja A aktualizuje statusy wybranych wierszy i zatwierdza transakcję.
  4. Oracle będzie teraz (automatycznie) ponownie uruchamiać wybór sesji B od początku, ponieważ dane zostały zmienione, a my określiliśmy FOR UPDATE (klauzula ta wymusza na Oracle pobranie ostatniej wersji bloku).
    Oznacza to, że sesja B otrzyma 10 nowych wierszy.

W tym scenariuszu nie występują problemy z konsystencją. Ponadto zakładając, że transakcja żądająca wiersza i zmiana jego statusu jest szybka, wpływ na współbieżność będzie niewielki.

+0

dziękuję, próbuję wykonać polecenie "WYBIERZ * Z IMPORTJOBS WHERE STATUSCODE =" ZAREJESTROWANY "I ROWNUM <= 1 DLA ZAKTUALIZOWANEGO ZAKUPU ZABLOKOWANEGO", ale wciąż zwraca ten sam wiersz z różnych procesów? –

+1

(1) upewnij się, że autocommit został wyłączony: nie można zablokować wiersza bez transakcji. (2) 'DLA ZAKTUALIZOWANEGO SKIP ZABLOKOWANEGO' i' rownum' [nie będzie działać zgodnie z oczekiwaniami] (http://stackoverflow.com/questions/5847228/oracle-select-for-update-behavour) - to dlatego, że SKIP LOCKED jest oceniany ** po ** klauzuli WHERE. Użyj opcji select bez rownum, pobierz jeden (lub więcej w razie potrzeby) wiersz i zamknij kursor, to najlepszy sposób użycia funkcji SKIP LOCKED. –

+0

rzeczywiście, musiałem umieścić select i update w transakcji, teraz działa. Dzięki!!! –

2

Każdy proces może wydać SELECT ... FOR UPDATE, aby zablokować wiersz podczas jego odczytu. W tym scenariuszu proces A odczyta i zablokuje wiersz, proces B spróbuje odczytać wiersz i zablokować, aż proces A zwolni blokadę, zatwierdzając (lub wycofując) swoją transakcję. Oracle następnie ustali, czy wiersz nadal spełnia kryteria B, a w twoim przykładzie nie zwróci wiersza do B. To działa, ale oznacza to, że twój proces wielowątkowy może być teraz efektywnie jednowątkowy w zależności od tego, jak kontrolujesz transakcję musi działać.

możliwych sposobów poprawy skalowalności

  • stosunkowo częstym podejście konsumenta do rozwiązania jest to, aby mieć jeden wątek koordynator, który odczytuje dane z tabeli, paczki z pracy do różnych wątków i aktualizuje tabela odpowiednio (w tym wiedzieć, jak ponownie przypisać zadanie, jeśli wątek, który został przypisany zmarł).
  • Jeśli korzystasz z Oracle 11.1 lub nowszego, możesz użyć SKIP LOCKED clause na swoim FOR UPDATE, aby każda sesja odzyskała pierwszy wiersz, który spełniał ich kryteria i nie był zablokowany (klauzula istniała we wcześniejszych wersjach, ale nie została udokumentowana, aby może nie działać poprawnie).
  • Zamiast używać tabeli dla ImportJobs, można użyć kolejki z wieloma klientami. Umożliwi to firmie Oracle dystrybucję komunikatów do każdego procesu bez konieczności tworzenia dodatkowego blokowania (kolejki Oracle robią to wszystko za kulisami).
1

Użyj versioning and optimistic concurrency.

Tabela IMPORTJOBS powinna mieć kolumnę znacznika czasu oznaczoną jako ConcurrencyMode = Fixed w swoim modelu. Teraz, gdy EF próbuje wykonać aktualizację, kolumna znacznika czasu zostaje włączona do oświadczenia aktualizacji: WHERE timestamp = xxxxx.

Dla znacznika czasu zmienionego w międzyczasie podniesiono wyjątek współbieżności, który w tym przypadku jest obsługiwany przez pomijanie aktualizacji.

Pochodzę z tła serwera SQL i nie znam odpowiednika znacznika czasu Oracle (ani wiersza polecenia), ale chodzi o to, że jest to pole, które automatycznie aktualizuje się po aktualizacji do rekordu.

Powiązane problemy