7. Zarządzanie równoczesnym dostępem do danych
Do tej pory korzystaliśmy z tabel, których byliśmy jedynymi użytkownikami. W praktyce, na komputerze obsługującym wielu użytkowników, dane są najczęściej współdzielone między różnymi użytkownikami. Powstaje zatem pytanie: kto może korzystać z danej tabeli i w jaki sposób (przeglądanie, wstawianie, usuwanie, dodawanie, ...)?
7.1. Tworzenie użytkowników Firebird
Podczas pracy z programem IB-Expert zalogowaliśmy się jako użytkownik SYSDBA. Informację tę można znaleźć we właściwościach otwartego połączenia o nazwie SGBD:
![]() | ![]() |
Po prawej stronie widać, że zalogowanym użytkownikiem jest [SYSDBA]. Nie widać jednak jego hasła [masterkey]. [SYSDBA] jest specjalnym użytkownikiem systemu Firebird: posiada pełne uprawnienia do wszystkich obiektów zarządzanych przez SGBD. Można tworzyć nowych użytkowników za pomocą IBExpert, korzystając z opcji [Tools / User Manager] lub następującej ikony:

Pojawi się okno zarządzania użytkownikami:

Przycisk [Add] umożliwia tworzenie nowych użytkowników:

Utwórzmy zatem następujących użytkowników:
nazwa | hasło |
ADMIN1 | admin1 |
ADMIN2 | admin2 |
SELECT1 | select1 |
SELECT2 | select2 |
UPDATE1 | aktualizacja1 |
UPDATE2 | update2 |
7.2. Przyznawanie uprawnień dostępu użytkownikom
Baza danych należy do osoby, która ją utworzyła. Bazy danych, które utworzyliśmy do tej pory, należały do użytkownika [SYSDBA]. Aby zilustrować pojęcie uprawnień, utwórzmy (Database / Create Database) nową bazę danych pod identyfikatorem [ADMIN1, admin1]:

i zapiszmy ją pod aliasem DBACCES (ADMIN1). Korzystanie z aliasów pozwala na nawiązywanie połączeń z tą samą bazą danych przy użyciu różnych identyfikatorów, co ułatwia ich identyfikację w eksploratorze baz danych IBExpert:
![]() | ![]() |
Teraz utwórzmy dwie następujące tabele: TA i TB:
Tabela TA
![]() |
Tabela TB
![]() |
Tabele te nie są ze sobą powiązane.
Za pomocą programu IB-Expert utworzymy drugie połączenie z bazą danych [DBACCES], tym razem pod nazwą [ADMIN2 / admin2]. W tym celu wykorzystamy opcję [Database / Register Database]:
![]() | ![]() |
Przejdźmy do pliku DBACCES (ADMIN2) i otwórzmy edytor SQL (Shift + F12):
![]() |
Będziemy mieli okazję korzystać z różnych połączeń w tej samej bazie danych [DBACCES]. Dla każdego z nich będziemy mieli edytor SQL. W [1] edytor SQL wskazuje alias podłączonej bazy danych. Skorzystaj z tej informacji, aby dowiedzieć się, w którym edytorze SQL się znajdujesz. Będzie to miało znaczenie, ponieważ będziemy tworzyć połączenia, które nie będą miały takich samych uprawnień dostępu do obiektów bazy danych.
Wyświetlmy zawartość tabeli TA:

Otrzymujemy następujący komunikat o błędzie:

Co to oznacza? Baza danych [DBACCESS] została utworzona przez użytkownika [ADMIN1] i w związku z tym jest jego własnością. Tylko on ma dostęp do różnych obiektów tej bazy. Może on przyznawać uprawnienia dostępu innym użytkownikom za pomocą polecenia SQL GRANT. Polecenie to ma różne składnie. Jedna z nich wygląda następująco:
GRANT uprawnienie1, uprawnienie2, ...| ALL PRIVILEGES ON table/vue TO użytkownik1, użytkownik2, ...| PUBLIC [ WITH GRANT OPTION ] | |
przyznaje uprawnienia dostępu privilègei lub wszystkie uprawnienia (ALL PRIVILEGES) na table lub vue dla użytkowników utilisateuri lub dla wszystkich użytkowników ( PUBLIC ). Klauzula WITH GRANT OPTION umożliwia użytkownikom, którzy otrzymali uprawnienia, przekazywanie ich z kolei innym użytkownikom. |
Wśród uprawnień privilègei, które można przyznać, znajdują się następujące:
uprawnienie do korzystania z polecenia DELETE w odniesieniu do tabeli lub widoku. | |
uprawnienie do korzystania z polecenia INSERT na tabeli lub widoku | |
uprawnienie do używania polecenia SELECT na tabeli lub widoku | |
uprawnienie do używania polecenia UPDATE na tabeli lub widoku. Uprawnienie to można ograniczyć do określonych kolumn za pomocą następującej składni: GRANT update (col1, col2, ...) ON tabela/widok TO użytkownik1, użytkownik2, ...| PUBLIC [ WITH GRANT OPTION ] |
Przyznajmy użytkownikowi [ADMIN2] uprawnienie SELECT do tabeli TA. Tylko właściciel tabeli może przyznać to uprawnienie, c.a.d. W tym przypadku jest to [ADMIN1]. Przejdźmy do połączenia DBACCES (ADMIN1) i otwórzmy nowy edytor SQL (Shift+F12):

Następnie będziemy przechodzić między edytorami SQL. Aby się w tym wszystkim odnaleźć, można skorzystać z opcji [Windows] w menu:

Powyżej widoczne są dwa edytory SQL, z których każdy jest przypisany do konkretnego użytkownika. Wróćmy do edytora SQL (ADMIN1) i wydajmy następujące polecenie:

Następnie zatwierdźmy je za pomocą COMMIT:

Po wykonaniu tych czynności przejdźmy do edytora użytkownika ADMIN2, aby ponownie wykonać polecenie SELECT, które zakończyło się niepowodzeniem:

Pojawia się następujący komunikat o błędzie:

Użytkownik [ADMIN2] nadal nie ma uprawnień do przeglądania tabeli [TA]. W rzeczywistości wydaje się, że uprawnienia użytkownika są ładowane w momencie logowania. [ADMIN2] miałby zatem nadal te same uprawnienia, co na początku sesji, czyli żadne. Sprawdźmy to. Wylogujmy użytkownika [ADMIN2]:
- przejdź do jego połączenia
- poprośmy o wylogowanie, klikając prawym przyciskiem myszy na połączeniu i wybierając opcję [Deconnect from database] lub (Shift + Ctrl + D)

Jeśli pojawi się okienko z prośbą o podanie [COMMIT], wpisz [COMMIT]. Następnie ponownie zalogujmy użytkownika [ADMIN2], wybierając powyższą opcję [Reconnect]. Po wykonaniu tej czynności wróćmy do edytora SQL (ADMIN2) i ponownie uruchommy żądanie SELECT, które zakończyło się niepowodzeniem:

Otrzymujemy wówczas następujący wynik:

Tym razem ADMIN2 może przeglądać tabelę TA dzięki uprawnieniu SELECT, które przyznał mu właściciel ADMIN1. Zazwyczaj jest to jedyne uprawnienie, jakie posiada. Sprawdźmy to. Nadal w edytorze SQL (ADMIN2):
![]() | ![]() |
Ekran po prawej stronie pokazuje, że ADMIN2 nie posiada uprawnienia DELETE w tabeli TA.
Wróćmy do edytora SQL (ADMIN1), aby przyznać użytkownikowi ADMIN2 szersze uprawnienia. Kolejno wydajemy dwa następujące polecenia:
![]() | ![]() |
- pierwsze polecenie przyznaje użytkownikowi ADMIN2 wszystkie uprawnienia dostępu do tabeli [TA], a ponadto możliwość przyznawania przez niego samych uprawnień (WITH GRANT OPTION)
- drugie polecenie zatwierdza poprzednie
Po wykonaniu tej czynności, podobnie jak poprzednio, odświeżmy połączenie użytkownika [ADMIN2] (Rozłącz / Połącz ponownie), a następnie w edytorze SQL (ADMIN2) wpiszmy następujące polecenia:
![]() | ![]() | ![]() |
ADMIN2 usunęło wszystkie wiersze z tabeli TA. Cofnijmy to usunięcie za pomocą polecenia ROLLBACK:
![]() | ![]() | ![]() |
Sprawdźmy, czy ADMIN2 może z kolei nadać uprawnienia do tabeli TA.
![]() | ![]() |
Teraz nawiążmy połączenie z bazą danych [DBACCES] (Database / Register database) pod nazwą [SELECT1 / select1], jednym z wcześniej utworzonych użytkowników, a następnie kliknijmy dwukrotnie na utworzony w ten sposób link w [Database Explorer]:
![]() | ![]() |
Przejdźmy do tego nowego połączenia i otwórzmy nowy edytor SQL (Shift + F12), aby wprowadzić w nim następujące polecenia:
![]() | ![]() |
Użytkownik SELECT1 posiada uprawnienie SELECT w tabeli TA. Czy ma możliwość przekazania tego uprawnienia użytkownikowi SELECT2?
![]() |
Operacja zakończyła się niepowodzeniem, ponieważ użytkownik SELECT1 nie otrzymał uprawnienia do przekazania uprawnienia SELECT, które otrzymał od użytkownika ADMIN2. Aby to było możliwe,użytkownik ADMIN2 użył klauzuli WITH GRANT OPTION w swoim zleceniu SQL GRANT. Zasady przekazywania są proste:
- użytkownik może przekazać jedynie te uprawnienia, które otrzymał, i nic więcej
- może je przekazać tylko wtedy, gdy otrzymał je wraz z uprawnieniem [WITH GRANT OPTION]
Przyznane uprawnienie można cofnąć za pomocą polecenia REVOKE:
REVOKE uprawnienie1, uprawnienie2, ...| ALL PRIVILEGES ON table/vue FROM użytkownik1, użytkownik2, ...| PUBLIC | |
usuwa uprawnienia dostępu privilègei lub wszystkie uprawnienia (ALL PRIVILEGES) na table lub vue dla użytkowników utilisateuri lub dla wszystkich użytkowników ( PUBLIC ). |
Spróbujmy. Wróćmy do edytora SQL pliku ADMIN2, aby cofnąć uprawnienie SELECT, które przyznaliśmy użytkownikowi SELECT1:
![]() | ![]() |
Rozłączmy, a następnie ponownie nawiążmy połączenie użytkownika SELECT1. Następnie w edytorze SQL (SELECT1) wyświetlmy zawartość tabeli TA:
![]() | ![]() |
Użytkownik SELECT1 rzeczywiście utracił uprawnienie do odczytu tabeli TA. Należy zauważyć, że to ADMIN2 nadał mu to uprawnienie, a ADMIN2 je cofnął. Jeśli ADMIN1 spróbuje mu je odebrać, nie zostanie zgłoszony żaden błąd, ale można następnie zauważyć, że SELECT1 zachował swoje uprawnienie SELECT.
Uprawnienie można przyznać wszystkim, stosując następującą składnię: GRANT uprawnienie(a) ON tabela / widok TO PUBLIC. Przyznajmy zatem wszystkim uprawnienie SELECT do tabeli TA. W tym celu można użyć ADMIN1 lub ADMIN2. Używamy ADMIN2:
![]() | ![]() |
Utwórzmy połączenie z bazą danych przy użyciu użytkownika USER1 / user1:
![]() | ![]() |
Po zalogowaniu się jako DBACCES (USER1) otwórzmy nowy edytor SQL (Shift + F12) i wpiszmy następujące polecenia:
![]() | ![]() |
Użytkownik USER1 posiada uprawnienie SELECT do tabeli TA.
7.3. Transakcje
7.3.1. Poziomy izolacji
Odchodzimy teraz od kwestii uprawnień dostępu do obiektów bazy danych, aby zająć się problemem współbieżnego dostępu do tych obiektów. Dwóch użytkowników posiadających wystarczające uprawnienia dostępu do obiektu bazy danych, na przykład tabeli, chce z niego korzystać w tym samym czasie. Co się wtedy dzieje?
Każdy użytkownik pracuje w ramach transakcji. Transakcja to sekwencja poleceń SQL, która jest wykonywana w sposób „atomowy”:
- albo wszystkie operacje zakończą się powodzeniem
- albo jedna z nich zakończy się niepowodzeniem, a wtedy wszystkie poprzednie zostaną cofnięte
Ostatecznie operacje w ramach transakcji albo zostały pomyślnie zastosowane, albo żadna z nich nie została zastosowana. Gdy użytkownik sam kontroluje transakcję (co ma miejsce w całym niniejszym dokumencie), zatwierdza ją za pomocą polecenia COMMIT lub anuluje za pomocą polecenia ROLLBACK.
Każdy użytkownik pracuje w ramach własnej transakcji. Zazwyczaj rozróżnia się cztery poziomy izolacji między różnymi użytkownikami:
- Uncommitted Read
- Committed Read
- Odczyt powtarzalny
- Serializable
Odczyt niezapisany
Ten tryb izolacji nazywany jest również „Dirty Read”. Oto przykład tego, co może się wydarzyć w tym trybie:
- użytkownik U1 rozpoczyna transakcję na tabeli T
- użytkownik U2 rozpoczyna transakcję na tej samej tabeli T
- użytkownik U1 modyfikuje wiersze w tabeli T, ale jeszcze ich nie zatwierdza
- użytkownik U2 „widzi” te zmiany i podejmuje decyzje na podstawie tego, co widzi
- użytkownik anuluje swoją transakcję za pomocą ROLLBACK
Widać, że w punkcie 4 użytkownik U2 podjął decyzję na podstawie danych, które później okażą się nieprawdziwe.
Committed Read
Ten tryb izolacji pozwala uniknąć powyższej pułapki. W tym trybie użytkownik U2 w kroku 4 nie „zobaczy” zmian wprowadzonych przez użytkownika U1 w tabeli T. Zobaczy je dopiero po tym, jak użytkownik U1 zakończy swoją transakcję.
W tym trybie, zwanym również „Unrepeatable Read”, mogą wystąpić następujące sytuacje:
- użytkownik U1 rozpoczyna transakcję na tabeli T
- użytkownik U2 rozpoczyna transakcję na tej samej tabeli T
- użytkownik U2 tworzy SELECT w celu obliczenia średniej z kolumny C wierszy tabeli T spełniających określony warunek
- użytkownik U1 modyfikuje (UPDATE) niektóre wartości w kolumnie C tabeli T i zatwierdza je (COMMIT)
- użytkownik U2 ponownie wykonuje tę samą operację SELECT, co w punkcie 3. Zauważy, że średnia w kolumnie C uległa zmianie z powodu modyfikacji wprowadzonych przez U1.
Teraz użytkownik U2 widzi tylko zmiany „zatwierdzone” przez U1. Jednak pozostając w tej samej transakcji, dwie identyczne operacje (3 i 5) dają różne wyniki. Termin „Unrepeatable Read” (odczyt niepowtarzalny) określa tę sytuację. Jest to kłopotliwa sytuacja dla kogoś, kto chce uzyskać stabilny obraz tabeli T.
Repeatable Read
W tym trybie izolacji użytkownik ma gwarancję uzyskania tych samych wyników odczytów z bazy, dopóki pozostaje w tej samej transakcji. Pracuje on na „migawce”, na której nigdy nie są odzwierciedlane zmiany wprowadzone przez inne transakcje, nawet te zatwierdzone. Zmiany te zobaczy dopiero wtedy, gdy sam zakończy swoją transakcję za pomocą polecenia COMMIT lub ROLLBACK.
Ten tryb izolacji nie jest jednak jeszcze idealny. Po wykonaniu operacji 3 opisanej powyżej wiersze przeglądane przez użytkownika U2 są zablokowane. Podczas operacji 4 użytkownik U1 nie będzie mógł modyfikować (UPDATE) wartości w kolumnie C tych wierszy. Może jednak dodawać wiersze (INSERT). Jeśli niektóre z dodanych wierszy spełniają warunek sprawdzany w kroku 3, operacja 5 da średnią inną niż ta uzyskana w kroku 3 z powodu dodanych wierszy.
Aby rozwiązać ten nowy problem, należy przejść do izolacji „Serializable”.
Serializable
W tym trybie izolacji transakcje są całkowicie odizolowane od siebie. Gwarantuje to, że wynik dwóch transakcji przeprowadzanych jednocześnie będzie taki sam, jak gdyby zostały one wykonane jedna po drugiej. Aby to osiągnąć, podczas operacji 4, w której użytkownik U1 chce dodać wiersze, które zmieniłyby wynik operacji SELECT użytkownika U1, zostanie on zablokowany. Pojawi się komunikat o błędzie informujący, że wstawienie nie jest możliwe. Stanie się to możliwe dopiero po zatwierdzeniu transakcji przez użytkownika U2.
Cztery poziomy izolacji transakcji SQL nie są dostępne we wszystkich instancjach SGBD. Firebird udostępnia następujące poziomy izolacji:
- snapshot: domyślny tryb izolacji. Odpowiada trybowi „Repeatable Read” określonym w standardzie SQL.
- committed read: odpowiada trybowi „committed read” standardu SQL
Ten poziom izolacji jest ustalany za pomocą polecenia SET TRANSACTION:
SET TRANSACTION [READ WRITE | READ ONLY] [WAIT|NOWAIT] ISOLATION LEVEL [SNAPSHOT | READ COMMITTED] | |
podkreślone słowa kluczowe to wartości domyślne READ WRITE: transakcja może odczytywać i zapisywać READ ONLY: transakcja może tylko odczytywać WAIT: w przypadku konfliktu między dwiema transakcjami ta, która nie mogła wykonać swojej operacji, czeka na zatwierdzenie drugiej transakcji. Nie może już wysyłać poleceń SQL. NOWAIT: transakcja, która nie mogła wykonać swojej operacji, nie jest blokowana. Otrzymuje komunikat o błędzie i może kontynuować pracę. ISOLATION LEVEL [SNAPSHOT | READ COMMITTED]: poziom izolacji |
Spróbujmy. W edytorze SQL (ADMIN1) wpisujemy następujące polecenie SQL:

Widzimy, że polecenie nie zostało zaakceptowane. Nie wiadomo dlaczego...
IB-Expert pozwala ustawić tryb izolacji w inny sposób. Kliknijmy prawym przyciskiem myszy na połączenie DBACCES(ADMIN1), aby wybrać opcję [Database Registration Info]:
![]() | ![]() |
Ekran po prawej stronie pokazuje, że dostępna jest opcja [Transactions]. Pozwoli nam ona ustawić poziom izolacji transakcji. Ustawiamy go tutaj na [snapshot]. To samo robimy w przypadku połączenia DBACCES (ADMIN2).
7.3.2. Tryb snapshot
Przyjrzyjmy się poziomowi izolacji snapshot, który jest domyślnym trybem izolacji w Firebirdzie. Gdy użytkownik rozpoczyna transakcję, tworzona jest migawka bazy danych. Użytkownik będzie następnie pracował na tej migawce. W ten sposób każdy użytkownik pracuje na własnej migawce bazy danych. Jeśli wprowadzi do niej zmiany, inni użytkownicy ich nie widzą. Zobaczą je dopiero wtedy, gdy użytkownik, który je wprowadził, zatwierdzi je za pomocą COMMIT.
Można rozważyć dwa przypadki:
- jeden użytkownik odczytuje tabelę (select), podczas gdy inny ją modyfikuje (insert, update, delete)
- obaj użytkownicy chcą jednocześnie modyfikować tabelę
7.3.2.1. Zasada spójnego odczytu
Załóżmy, że dwóch użytkowników U1 i U2 pracuje nad tą samą tabelą TAB:
Transakcja użytkownika U1 rozpoczyna się w momencie T1a i kończy się w momencie T1b.
Transakcja użytkownika U2 rozpoczyna się w momencie T2a i kończy się w momencie T2b.
U1 przetwarza zdjęcie TAB wykonane w momencie T1a. W okresie między T1a a T1b wprowadza zmiany w TAB. Inni użytkownicy będą mieli dostęp do tych zmian dopiero w momencie T1b, kiedy to U1 wykona COMMIT.
U2 pracuje nad zdjęciem TAB wykonanym w momencie T2a, a więc to samo zdjęcie, z którego korzystał U1 (o ile inni użytkownicy nie zmodyfikowali w międzyczasie oryginału). Nie „widzi” on zmian, jakie użytkownik U1 mógł wprowadzić w pliku TAB. Będzie mógł je zobaczyć dopiero w momencie T1b.
Zilustrujmy to na przykładzie naszej bazy [DBACCES]. Sprawimy, że obaj użytkownicy, [ADMIN1] i [ADMIN2], będą pracować jednocześnie. Przejdźmy do połączenia DBACCES (ADMIN1) i w edytorze SQL użytkownika ADMIN1 wykonajmy następujące czynności:
![]() | ![]() | ![]() |
ADMIN1 zmodyfikował wiersz nr 2 w tabeli TA, ale nie zatwierdził jeszcze (COMMIT) swojej operacji. Następnie użytkownik ADMIN2 wykonuje operację SELECT na tabeli TA (przechodzimy do edytora SQL z ADMIN2). Znajdujemy się przed momentem T2a z przykładu.
![]() | ![]() |
Powrót do edytora SQL z pliku ADMIN1, który zatwierdza dodanie:
![]() |
Powrót do edytora SQL z pliku ADMIN2 w celu ponownego utworzenia pliku SELECT:
![]() | ![]() |
ADMIN2 widzi zmiany wprowadzone przez ADMIN1. W trybie migawki transakcja nie widzi zmian wprowadzonych przez inne transakcje, dopóki te ostatnie nie zostaną zakończone.
7.3.2.2. Jednoczesna modyfikacja tego samego obiektu bazy danych przez dwie transakcje
Weźmy przykład z księgowości: U1 i U2 pracują nad kontami. U1 obciąża konto comptex kwotą S i uznaje konto comptey tą samą kwotą. Dokona tego w kilku etapach:
U1 rozpoczyna transakcję w momencie T1a, obciąża konto comptex w momencie T1b, uznaje konto comptey w momencie T1c i zatwierdza obie operacje w momencie T1d. Załóżmy ponadto, że U2 chce wykonać tę samą czynność, rozpoczyna swoją transakcję w momencie T2a i kończy ją w momencie T2d zgodnie z poniższym schematem:
--------+----------+----+----+-------+------+-----+-------+---------
T1a T1b T2a T1c T2b T1d T2c T2d
W momencie T2 wykonano migawkę tabeli kont dla U2. Jest ona spójna zgodnie z zasadą snapshot. U2 widzi stan początkowy kont comptex i comptey, ponieważ U1 nie zatwierdziło jeszcze swoich operacji.
Załóżmy, że comptex ma saldo początkowe w wysokości 1000 € i że każdy z użytkowników U1 oraz U2 chce obciążyć to konto kwotą 100 €.
- W momencie T1b U1 zmniejsza saldo comptex o 100 €, obniżając je tym samym do 90 €. Operacja ta zostanie zatwierdzona dopiero w momencie T1d.
- W momencie T2b U2 widzi, że comptex ma 1000 € (zasada spójnego odczytu), zmniejsza jego stan o 100 €, a zatem ustawia go na 90 €.
- Ostatecznie, w momencie T2d, gdy wszystko zostanie zatwierdzone, saldo konta comptex wyniesie 90 € zamiast oczekiwanych 80 €.
Rozwiązaniem tego problemu jest uniemożliwienie U2 modyfikowania comptex, dopóki U1 nie zakończy swojej transakcji. W ten sposób U2 zostanie zablokowany do momentu T1d. Mechanizm ten zapewnia tryb snapshot.
Zilustrujmy to na przykładzie bazy DBACCES. ADMIN1 rozpoczyna transakcję w swoim edytorze SQL (ADMIN1):
![]() | ![]() | ![]() | ![]() |
Zaczęliśmy od utworzenia transakcji COMMIT, aby mieć pewność, że rozpoczynamy nową transakcję. Następnie usunęliśmy wiersz nr 4. Transakcja nie została jeszcze zatwierdzona.
Z kolei ADMIN2 rozpoczyna transakcję w edytorze SQL (ADMIN2):
![]() | ![]() |
Ekran po prawej stronie pokazuje, że ADMIN2 próbował zmodyfikować wiersz nr 4. Otrzymał komunikat, że nie jest to możliwe, ponieważ ktoś inny już go zmodyfikował, ale jeszcze nie zatwierdził tej zmiany.
Wróćmy do edytora SQL (ADMIN1), aby utworzyć plik COMMIT:

Wróćmy do edytora SQL (ADMIN2), aby ponownie wykonać polecenie UPDATE:
![]() | ![]() |
![]() | ![]() |
Operacja UPDATE przebiega pomyślnie, mimo że wiersz nr 4 już nie istnieje, co pokazuje następujące po niej polecenie SELECT. W tym momencie polecenie ADMIN2 wykrywa, że wiersz już nie istnieje.
7.3.2.3. Tryb Repeatable Read
Zilustrujmy teraz tryb „Repeatable Read”. Ten poziom izolacji zapewnia tryb „snapshot”. Gwarantuje on, że transakcja zawsze uzyska ten sam wynik podczas odczytu bazy danych.
Zacznijmy od pracy z edytorem SQL pliku ADMIN2:
![]() | ![]() | ![]() |
![]() | ![]() |
Przejdźmy teraz do edytora SQL z ADMIN1:
![]() | ![]() | ![]() |
![]() | ![]() | ![]() |
![]() | ![]() |
Użytkownik ADMIN1 dodał dwa wiersze i zatwierdził swoją transakcję. Wróćmy teraz do edytora SQL (ADMIN2), aby odtworzyć SELECT SUM:
![]() | ![]() |
Widać, że ADMIN2 nie uwzględnia dodanych wierszy z pliku ADMIN1, mimo że zostały one zatwierdzone przez plik COMMIT. SELECT SUM daje ten sam wynik, co przed dodaniem wierszy. Na tym polega zasada powtarzalnego odczytu (Repeatable Read).
Teraz, nadal w edytorze SQL (ADMIN2), zatwierdźmy transakcję za pomocą COMMIT, a następnie ponownie uruchommy SELECT i SUM:
![]() | ![]() | ![]() |
Wiersze dodane przez ADMIN1 są teraz uwzględniane.
7.3.3. Tryb Committed Read
Przyjrzyjmy się teraz trybowi „Committed Read”. Ten poziom izolacji jest analogiczny do poziomu snapshot, z wyjątkiem trybu „Repeatable Read”.
Zaczynamy od zmiany poziomu izolacji transakcji dla obu połączeń.
- odłączamy obu użytkowników ADMIN1 i ADMIN2
- zmieniamy poziom izolacji ich transakcji

- ponownie logujemy użytkowników ADMIN1 i ADMIN2
Wróćmy teraz do poprzedniego przykładu ilustrującego „Repeatable Read”, aby pokazać, że nie obserwujemy już tego samego zachowania. Zacznijmy od pracy z edytorem SQL z ADMIN2:
![]() | ![]() | ![]() |
![]() | ![]() |
Przejdźmy teraz do edytora SQL z ADMIN1:
![]() | ![]() | ![]() |
![]() | ![]() | ![]() |
![]() | ![]() |
Użytkownik ADMIN1 dodał dwa wiersze i zatwierdził swoją transakcję. Wróćmy teraz do edytora SQL (ADMIN2), aby ponownie odtworzyć SELECT SUM:
![]() | ![]() |
SELECT SUM nie daje takiego samego wyniku jak przed wprowadzeniem zmian przez ADMIN1. Jest to różnica między trybami snapshot a read committed.








































































