Utwórz bazę danych Cw: CREATE DATABASE Cw Sprawdzenie poziomu izolacji transakcji (w aktualnym połączeniu): DBCC USEROPTIONS Przykład z zapisem do tabeli tymczasowej: --Jeśli istnieje tabela tymczasowa #traceoptions to ją skasuj: IF EXISTS (SELECT name from tempdb.sys.objects WHERE name like N'#traceoptions%') BEGIN DECLARE @tname varchar(255) SET @tname= (SELECT name from tempdb.sys.objects WHERE name like N'#traceoptions%') EXEC ('DROP TABLE ' + @tname ) select @@error END --Stwórz tabelę tymczasową o nazwie #traceoptions: CREATE TABLE #traceoptions ( OptionName varchar(50), Value varchar(50) ) INSERT INTO #traceoptions EXEC ('DBCC useroptions') -- Wyświetl wynik z tabeli (jeden wiersz). SELECT * FROM #traceoptions where OptionName='isolation level' Przykład z zapisem do zmiennej tabelowej: DECLARE @traceoptions TABLE ( OptionName varchar(50), Value varchar(50) ) INSERT INTO @traceoptions EXEC ('DBCC useroptions') SELECT * FROM @traceoptions WHERE OptionName='isolation level' Przykład ustawienia poziomu izolacji, jeśli obecnie jest inny niż SERIALIZABLE: DECLARE @traceoptions TABLE ( OptionName varchar(50), Value varchar(50) ) INSERT INTO @traceoptions EXEC ('DBCC useroptions') IF NOT EXISTS (SELECT * FROM @traceoptions WHERE OptionName='isolation level' AND Value = 'serializable')
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE lub równoważnie: DECLARE @traceoptions TABLE ( OptionName varchar(50), Value varchar(50) ) INSERT INTO @traceoptions EXEC ('DBCC useroptions') IF (SELECT Value FROM @traceoptions WHERE OptionName='isolation level') <> 'serializable' SET TRANSACTION ISOLATION LEVEL SERIALIZABLE Zadanie 1. Sprawdź różnicę między poziomem izolacji transakcji READ UNTED oraz READ TED. W programie Management Studio należy otworzyd dwa okna (połączenia) do używanej bazy danych (Cw). Proszę utworzyd tabelę T1(p1 INT PRIMARY KEY, p2 INT): CREATE TABLE T1(p1 INT PRIMARY KEY, p2 INT) i wpisad do niej trzy wiersze (1,100), (2,200), (3,300): INSERT INTO t1 VALUES(1,100) INSERT INTO t1 VALUES(2,200) INSERT INTO t1 VALUES(3,300) Proszę utworzyd tabelę T2(p1 INT, p2 INT) i pozostawid ją pustą. CREATE TABLE T2(p1 INT PRIMARY KEY, p2 INT) -- Domyślnie poziom jest READ TED USE cw DELETE FROM T2 UPDATE T1 SET p2='111' WHERE p1=1 SELECT * FROM T1 PRINT @@trancount USE Cw SET TRANSACTION ISOLATION LEVEL READ UNTED
DECLARE @w1 INT DECLARE @w2 INT SELECT @w1=p1, @w2=p2 FROM T1 WHERE p1=1 INSERT INTO T2 values(@w1, @w2) -- W ten sposób tymczasowe dane z T1 zostały trwale wpisane do tabeli T2. ROLLBACK SELECT * FROM T2 SELECT * FROM T1 W powyższym przykładzie w pierwszym połączeniu modyfikowany jest wiersz 1 w tabeli T1, w drugim połączeniu wiersz ten jest odczytywany i (poprzez zmienne) zapisywany do tabeli T2. Następnie w połączeniu 1 transakcje jest wycofywana, ale jej przejściowe wyniki będą już trwałe w tabeli T2 poprzez transakcję 2, która odczytała niezatwierdzone dane. Te same instrukcje wykonane w takie samej kolejności, przy ustawieniu w połączeniu 2 (Connection 2) poziomu izolacji transakcji na READ TED spowodują, że proces w połączeniu 2 zostanie zawieszony (instrukcja SELECT) w oczekiwaniu na zakooczenie transakcji z połączenia 1. Proszę powtórzyd powyższe polecenia, ale z ustawionym poziomem izolacji transakcji READ TED (SET TRANSACTION ISOLATION LEVEL READ TED). Zadanie 2. Sprawdź zjawisko Fuzzy Read w poziomie izolacji READ TED (jest to poziom domyślny w systemie Microsoft SQL Server). Przykład pokaże zjawisko niespójnej analizy. W bazie danych Cw utwórz tabelę Konta( nr_konta INT PRIMARY KEY, nr_klienta INT, stan MONEY). Wstaw tam trzy wiersze: 123, 1, 1000 124, 2, 1000 125, 1, 1000. CREATE TABLE Konta( nr_konta INT PRIMARY KEY, nr_klienta INT, stan MONEY) INSERT INTO Konta VALUES (123,1,1000) INSERT INTO Konta VALUES (124,2,1000) INSERT INTO Konta VALUES (125,1,1000) Klient o numerze 1 ma na dwóch kontach łącznie 2000. Osoba A będzie odczytywała wartośd na kontach klienta 1, osoba B będzie wykonywała przelew 100 z konta 123 na konto 125. Osoba A będzie pracowała przez połączenie 1, osoba B przez połączenie 2. Otwórz dwa połączenia, będą one oznaczane Connection 1 i Connection 2.
Na początek należy zapewnid poziom izolacji transakcji READ TED w obydwu połączeniach. SET TRANSACTION ISOLATION LEVEL READ TED CREATE TABLE #tmp(suma MONEY) INSERT INTO #tmp SELECT stan FROM Konta WHERE nr_konta=123 SET TRANSACTION ISOLATION LEVEL READ TED UPDATE Konta SET Stan = Stan - 100 WHERE nr_konta=123 UPDATE Konta SET Stan = Stan + 100 WHERE nr_konta=125 UPDATE #tmp SET suma = suma + (SELECT Stan FROM Konta WHERE nr_konta=125) SELECT * FROM #tmp Suma na kontach zapisana w tabeli tymczasowej #tmp wynosi 2100 zamiast 2000. Sposób rozwiązania problem: ustawid poziom izolacji transakcji REPEATABLE READ albo SERIALIZABLE. Uwaga! Problem niespójnej analizy, który się tutaj pojawił można by czasem uznad za mniej istotny, ponieważ w tabeli Konta po wykonaniu obydwu transakcji żadne kwoty nie przepadły ani nie pojawiły się znikąd. Transakcja, która otrzymała niepoprawne (niespójne) dane tylko te dane odczytywała (chociaż oczywiście to może również byd źródłem poważnych kłopotów) Poniższe zadanie pokaże byd może poważniejszy problem utraconej aktualizacji. Zadanie 3. Problem utraconej aktualizacji. Tym razem osoba A poprzez połączenie 1 wybierze 100 z konta 123. W tym samym czasie osoba B wykona taką samą operację. -- Doprowadzenie tabeli do sytuacji jak na początku poprzedniego zadania UPDATE Konta SET Stan = 1000 SET TRANSACTION ISOLATION LEVEL READ TED DROP TABLE #tmp1
CREATE TABLE #tmp1(suma MONEY) SET TRANSACTION ISOLATION LEVEL READ TED DROP TABLE #tmp2 CREATE TABLE #tmp2(suma MONEY) INSERT INTO #tmp1 SELECT stan FROM Konta WHERE nr_konta=123 INSERT INTO #tmp2 SELECT stan FROM Konta WHERE nr_konta=123 UPDATE Konta SET Stan = (SELECT suma FROM #tmp2) - 100 WHERE nr_konta=123 UPDATE Konta SET Stan = (SELECT suma FROM #tmp1) - 100 WHERE nr_konta=123 SELECT * FROM Konta Pomimo, że dwie osoby poprawnie wybrały po 100 z konta nr 123, na koncie zostało 900 (przy stanie początkowym 1000). Sposób rozwiązania problem: ustawid poziom izolacji transakcji REPEATABLE READ albo SERIALIZABLE. Zadanie 4. Wykonaj zadania 2 i 3 z poziomem izolacji transakcji SERIALIZABLE. Powyższe problemy nie wystąpią, ale w pewnych momentach jedna z transakcji zostanie wstrzymana aż do zakooczenia drugiej. Jest to spowodowane przez zakładanie blokad. Zadanie 5. Przy wielu współbieżnych transakcjach działających na wspólnych danych blokady mogą byd przyczyną niskiej wydajności systemu. Radą może byd zastosowanie poziomu izolacji transakcji SNAPSHOT. Wykonaj zadanie 2 z poziomem izolacji transakcji SNAPSHOT. -- Doprowadzenie tabeli do sytuacji jak na początku poprzedniego zadania UPDATE Konta SET Stan = 1000 USE MASTER
USE MASTER ALTER DATABASE Cw SET ALLOW_SNAPSHOT_ISOLATION ON USE Cw DROP TABLE #tmp CREATE TABLE #tmp(suma MONEY) SET TRANSACTION ISOLATION LEVEL SNAPSHOT USE Cw DROP TABLE #tmp CREATE TABLE #tmp(suma MONEY) SET TRANSACTION ISOLATION LEVEL SNAPSHOT INSERT INTO #tmp SELECT stan FROM Konta WHERE nr_konta=123 UPDATE Konta SET Stan = Stan - 100 WHERE nr_konta=123 UPDATE Konta SET Stan = Stan + 100 WHERE nr_konta=125 UPDATE #tmp SET suma = suma + (SELECT Stan FROM Konta WHERE nr_konta=125) SELECT * FROM #tmp Tym razem suma na kontach zapisana w tabeli tymczasowej #tmp jest poprawna, wynosi 2000. Zadanie 6. Wykonaj zadanie 3 z poziomem izolacji transakcji SNAPSHOT. Osoba A poprzez połączenie 1 wybierze 100 z konta 123. W tym samym czasie osoba B wykona taką samą operację. -- Doprowadzenie tabeli do sytuacji jak na początku poprzedniego zadania
UPDATE Konta SET Stan = 1000 DROP TABLE #tmp1 CREATE TABLE #tmp1(suma MONEY) DROP TABLE #tmp2 CREATE TABLE #tmp2(suma MONEY) INSERT INTO #tmp1 SELECT stan FROM Konta WHERE nr_konta=123 INSERT INTO #tmp2 SELECT stan FROM Konta WHERE nr_konta=123 UPDATE Konta SET Stan = (SELECT suma FROM #tmp2) - 100 WHERE nr_konta=123 UPDATE Konta SET Stan = (SELECT suma FROM #tmp1) - 100 WHERE nr_konta=123 W tym momencie transakcja zostanie przerwana i wycofana, zatem problem niespójności danych w tabeli Konta nie pojawi się. Zadanie 7. Sprawdź sposób sterowania współbieżnością przy użyciu kursorów. W bazie danych Cw utwórz tabelę T1 i wstaw do niej wiersze: CREATE TABLE T1(P1 INT, P2 INT) INSERT INTO T1 VALUES(1,100) INSERT INTO T1 VALUES(2,200) INSERT INTO T1 VALUES(3,300) Otwórz drugie połączenie w programie Management Studio. W obydwu połączeniach ustaw poziom izolacji transakcji READ TED. Poniższe instrukcje ilustrują problem utraconej modyfikacji (występował przy poziomie izolacji transakcji READ TED). Wykonaj następujące instrukcje: DECLARE @P1 INT, @P2 INT DECLARE C CURSOR DYNAMIC OPTIMISTIC FOR SELECT * FROM T1 OPEN C FETCH NEXT FROM C INTO @P1,@P2
SELECT @P1,@P2 Następnie przejdź do drugiego połączenia i wykonaj: UPDATE T1 SET P2=111 WHERE P1=1 Potem przejdź znowu do połączenia 1 i kontynuuj transakcję: UPDATE T1 SET P2=1000 WHERE CURRENT OF C (to polecenie powoduje aktualizacją bieżącego wiersza) Czy udało się wykonad? Nie pojawia się komunikat: Optimistic concurrency check failed. The row was modified outside of this cursor. Msg 16947, Level 16, State 1, Line 1 No rows were updated or deleted. The statement has been terminated. Zamknij kursor, zwolnij pamięd i zakoocz transakcję: CLOSE C DEALLOCATE C UWAGA! Taki sam efekt uzyskalibyśmy, gdyby operacje były wykonane nie w transakcjach. Wykorzystany tu poziom izolacji transakcji ma tylko nie przeszkadzad w rozpoznaniu mechanizmów sterowania współbieżnością poprzez kursory. Doprowadź tabelę T1 o poprzedniej postaci: UPDATE T1 SET P2=P1*100 Powtórz całe dwiczenie (od deklaracji kursora), ale tym razem w drugim połączeniu wstaw nieco zmienioną aktualizację: UPDATE T1 SET P2=100 WHERE P1=1 Tym razem aktualizacja przez kursor powiedzie się. Nie zapomnij zamknąd kursor, zwolnid pamięd i zakooczyd transakcję: CLOSE C DEALLOCATE C
Słowo OPTIMISTIC w deklaracji kursora oznacza, że będzie działało optymistyczne sterowanie współbieżnością ze sprawdzaniem wartości (OPTIMISTIC WITH VALUES). W takim przypadku przy próbie aktualizacji wiersza z wykorzystaniem kursora, przed wykonaniem aktualizacji system sprawdza, czy wiersz ma taką samą postad, jak w momencie pobrania wiersza (tj. wykonania instrukcji FETCH). Zadanie 8 Dodaj do tabeli T1 kolumnę typu timestamp: ALTER TABLE T1 ADD P3 TIMESTAMP Wykonaj całe dwiczenie 1 jeszcze raz, tym razem ze zmienioną tabelą T1. Teraz w obu przypadkach aktualizacja przez kursor nie powiedzie się. Objaśnienie: w przypadku, gdy w tabeli istnieje kolumna typu timestamp, jej wartośd jest wypełniana automatycznie przy dowolnej zmianie wiersza. Optymistyczne sterowanie współbieżnością w kursorach działa wtedy inaczej: zamiast sprawdzania wartości w kolumnach wiersza, sprawdzana jest wartośd w kolumnie timestamp. Zadanie 9 Usuo kolumnę p3 z tabeli T1: ALTER TABLE T1 DROP COLUMN P3 Wykonaj całe dwiczenie 1 z dwiema różnicami: inaczej zdefiniuj kursor, oraz wykonaj operacje nie w transakcjach: DECLARE @P1 INT, @P2 INT DECLARE C CURSOR DYNAMIC SCROLL_LOCKS FOR SELECT * FROM T1 OPEN C FETCH NEXT FROM C INTO @P1,@P2 SELECT @P1,@P2 Tym razem aktualizacja w drugim połączeniu będzie wstrzymana. Na wiersz pobrany do kursora w połączeniu 1 zakładana jest blokada U. Blokada zostaje zwolniona w momencie przejścia do innego wiersza lub w momencie zamknięcia kursora. W przypadku, gdyby operacje były wykonane w transakcjach, to na wiersz byłyby zakładane blokady U (długie, czyli do kooca transakcji).