Witaj na Zine.net online Zaloguj się | Rejestracja | Pomoc

[PL] Mój log jest za duży

Niedawno pisałem o tym, jak poradzić sobie z problemami, jakie mogą wyniknąć ze stosowania metody zmniejszania pliku dziennika transakcji bazy danych w SQL Server polegającej na usunięciu pliku loga.

Tym razem skupię się na metodzie najbardziej prawidłowej i właściwie jedynej zalecanej – wykorzystującej kombinację poleceń BACKUP LOG + DBCC SHRINKFILE.

Do dzieła!

Na początek utwórzmy mały poligon, na którym pokażemy w obrazkach, jak to się robi.

CREATE DATABASE TestLogDB
GO

ALTER DATABASE TestLogDB SET RECOVERY FULL
GO

USE TestLogDB
GO

CREATE TABLE TestLogTable
(
    col1 int identity(1,1),
    col2 nvarchar(4000) DEFAULT (REPLICATE(N'a',4000))
)
GO

BACKUP DATABASE TestLogDB
TO DISK = N'C:\TestLogDBFull.bak'
GO

INSERT TestLogTable DEFAULT VALUES
GO 1000

W nowo utworzonej bazie TestLogDB, której ustawiam recovery model na FULL (celowo robię to jawnie, ponieważ nie na każdym serwerze będzie to domyślne ustawienie dal nowych baz danych – patrz Express Edition lub sytuacja, w której ktoś zmienił opcję dla bazy model). Pełny backup bazy robię, gdyż tylko tak baza wchodzi w rzeczywisty FULL recovery model (backup jest punktem zaczepienia do rejestrowania zmian w logu). Wreszcie tworzę prostą tabelę i wypełniam ją rekordami (tu używam mechanizmu dostępnego w SSMS – wsad może wykonać się określoną ilość razy).

Zobaczmy teraz, jak wygląda log bazy. Na początek jego rozmiar:

DBCC SQLPERF(LOGSPACE)
GO

Database Name Log Size (MB) Log Space Used (%) Status
------------- ------------- ------------------ -----------
TestLogDB     9,929688      95,65794           0

Czyli log urósł (na początku miał 1MB) i na dodatek jest prawie pełny (czyli za moment prawdopodobnie znów się zwiększy).

A więc masz za dużego (?) loga bazy i chcesz go zmniejszyć :-)

Zmniejszamy loga

Zobaczmy VLF-y – czyli Virtual Log Files – czyli logiczne pliki dziennika transakcji, na które podzielony jest plik .ldf (użyję do tego nieudokumentowanego polecenia DBCC LOGINFO):

DBCC LOGINFO(N'TestLogDB')
GO

FileId      FileSize             StartOffset          FSeqNo      Status      Parity CreateLSN
----------- -------------------- -------------------- ----------- ----------- ------ -----------------
2           253952               8192                 27          2           64     0
2           253952               262144               28          2           64     0
2           270336               516096               29          2           64     0
2           262144               786432               30          2           64     29000000024900007
2           262144               1048576              31          2           64     30000000023100007
...
2           253952               8585216              56          2           64     55000000027200011
2           253952               8839168              57          2           64     55000000027200011
2           344064               9093120              58          2           64     55000000027200011
2           253952               9437184              59          2           64     58000000039300011
2           253952               9691136              60          2           64     58000000039300011

2           475136               9945088              0           0           0      58000000039300011

Poza tym, że jest ich trochę (w moim przykładzie było 35, a na samym początku, przed wstawieniem rekordów były ledwie 3) można o nich powiedzieć jeszcze jedno – prawie wszystkie są zajęte (status = 2 oznacza zajęty VLF, zaznaczyłem je na czerwono). Wolny pozostał tylko ostatni (status = 0 oznacza wolny VLF, zaznaczyłem go na zielono).

Co zrobić, żeby “opróżnić” log? Jak nakazują dobre praktyki i prawie wszystkie “guide’y”, należy wykonać backup loga.

BACKUP LOG TestLogDB
TO DISK = N'C:\TestLogDBLog.bak'

Backup zrobiony, zobaczmy ponownie rozmiar loga:

DBCC SQLPERF(LOGSPACE)
GO

Database Name Log Size (MB) Log Space Used (%) Status
------------- ------------- ------------------ -----------
TestLogDB     9,929688      24,69512           0

Rozmiar bez zmian, ale tym razem log jest zajęty w niewielkiej części – czyli nowe transakcje mają się gdzie logować.

Zobaczmy VLF-y:

DBCC LOGINFO(N'TestLogDB')
GO

FileId      FileSize             StartOffset          FSeqNo      Status      Parity CreateLSN
----------- -------------------- -------------------- ----------- ----------- ------ -----------------
2           253952               8192                 27          0           64     0
2           253952               262144               28          0           64     0
2           270336               516096               29          0           64     0
2           262144               786432               30          0           64     29000000024900007
2           262144               1048576              31          0           64     30000000023100007
...
2           253952               8585216              56          0           64     55000000027200011
2           253952               8839168              57          0           64     55000000027200011
2           344064               9093120              58          0           64     55000000027200011
2           253952               9437184              59          0           64     58000000039300011

2           253952               9691136              60          2           64     58000000039300011
2           475136               9945088              0           0           0      58000000039300011

Liczba VLF-ów się nie zmieniła (u mnie 35). Natomiast zmieniły się statusy. Widać, że zaznaczone na pomarańczowo VLF-y mają status 0, czyli są wolne i mogą przyjąć nowe wpisy – dziennik transakcji może być wypełniany od początku (angielskie określenie na takiego zachowania to log reuse), jeśli na początku pliku .ldf znajdują się wolne VLF-y. Dwa ostatnie VLF-y w pliku pozostały bez zmian.

Czas na zmniejszenie loga. Spróbujmy użyć polecenia DBCC SHRINKFILE.

DBCC SHRINKFILE(N'TestLogDB_log')
GO

Cannot shrink log file 2 (TestLogDB_log) because all logical log files are in use.
DbId   FileId      CurrentSize MinimumSize UsedPages   EstimatedPages
------ ----------- ----------- ----------- ----------- --------------
18     2           1214        96          1208        96

Hmmm, ale o co chodzi? :-)

Zobaczmy, czy log się zmniejszył (miał ponad 9MB).

DBCC SQLPERF(LOGSPACE)
GO

Database Name Log Size (MB) Log Space Used (%) Status
------------- ------------- ------------------ -----------
TestLogDB     9,476563      28,36459           0

A zatem praktycznie bez zmian… Można by pomyśleć, że coś źle robimy… Zobaczmy VLF-y.

DBCC LOGINFO(N'TestLogDB')
GO

FileId      FileSize             StartOffset          FSeqNo      Status      Parity CreateLSN
----------- -------------------- -------------------- ----------- ----------- ------ ---------------------------------------
2           253952               8192                 61          2           128    0
2           253952               262144               62          2           128    0

2           270336               516096               29          0           64     0
2           262144               786432               30          0           64     29000000024900007
2           262144               1048576              31          0           64     30000000023100007
2           262144               1310720              32          0           64     31000000023100007
...
2           344064               9093120              58          0           64     55000000027200011
2           253952               9437184              59          0           64     58000000039300011

2           253952               9691136              60          2           64     58000000039300011

Po pierwsze liczba VLF-ów się zmniejszyła (u mnie został usunięty ostatni VLF i mam ich teraz 34). A więc polecenie DBCC SHRINKFILE coś jednak zrobiło! Ostatnim zajętym VLF-em jest teraz drugi VLF w pliku .ldf. A więc przez wykonanie próby zmniejszenia loga tenże log się “obrócił” i teraz będzie wypełniany od początku.

Jeśli teraz wykonamy ponownie backup loga:

BACKUP LOG TestLogDB
TO DISK = N'C:\TestLogDBLog.bak'

to być może okaże się (nie jest to regułą, o czym przeczytasz we wnioskach), że nasz plik .ldf będzie wreszcie gotowy do zmniejszenia:

DBCC LOGINFO(N'TestLogDB')
GO

FileId      FileSize             StartOffset          FSeqNo      Status      Parity CreateLSN
----------- -------------------- -------------------- ----------- ----------- ------ -----------------
2           253952               8192                 61          0           128    0
2           253952               262144               62          2           128    0
2           270336               516096               29          0           64     0
2           262144               786432               30          0           64     29000000024900007
2           262144               1048576              31          0           64     30000000023100007
...
2           344064               9093120              58          0           64     55000000027200011
2           253952               9437184              59          0           64     58000000039300007
2           253952               9691136              60          0           64     58000000039300007

Liczba VLF-ów – bez zmian. Ale tym razem począwszy od trzeciego wszystkie są wolne i gotowe do usunięcia.

Dopełnienie formalności:

DBCC SHRINKFILE(N'TestLogDB_log')
GO

Database Name Log Size (MB) Log Space Used (%) Status
------------- ------------- ------------------ -----------
TestLogDB     0,7421875     38,55263           0

Uff… Ileż się trzeba było napracować. Ale wreszcie sukces (?) – log jest mniejszy (gdyby sprawdzić liczbę VLF-ów, to okazałoby się, że jest ich bardzo niewiele – na przykład 3). I teraz…

I teraz rekomendowana jest operacja ALTER DATABASE ... MODIFY FILE ... na pliku logów, by zwiększyć go do właściwego rozmiaru (zalecane od 10 do 25% rozmiaru plików danych bazy – ale wszystko zależy od paru czynników, m.in. od ilości transakcji w bazie, ilości użytkowników, etc.).

Wnioski i zalecenia

Cały ten ciąg czynności miał na celu wyciągnięcie niekoniecznie banalnych wniosków oraz przedstawienie pewnych zaleceń przydatnych w pracy z logiem:

  1. DBCC SHRINKFILE na pliku loga działa od razu, a nie z opóźnieniem, jak czasem można sądzić (choć jego działanie może być opóźnione przez aktywne transakcje lub inne operacje wykonywane na logu).
  2. DBCC SHRINKFILE nie przesuwa niczego w pliku .ldf (w plikach danych potrafi przemieszczać dane ku początkowi pliku).
  3. DBCC SHRINKFILE w pliku .ldf działa na VLF-ach (VLF jest atomową jednostką).
  4. Przed wykonaniem DBCC SHRINKFILE robimy backup dziennika transakcji (żeby go opróżnić).
  5. Jeśli nie uda się raz zmniejszyć loga tą metodą, trzeba spróbować drugi :-) A jak nie uda się za drugim razem, to i trzeci (a bywa, że i czwarty – musisz “wstrzelić się” z DBCC SHRINKFILE w moment, gdy ostatni aktywny VLF leży na początku pliku .ldf).
  6. Operacje zmniejszania loga najlepiej jest przeprowadzać przy braku aktywności użytkowników w bazie (można ustawić tryb SINGLE_USER). Wtedy mamy pełną kontrolę nad tym, co dzieje się w logu.
  7. Jeśli pokazana metoda zawodzi notorycznie, sprawdź, czy nie ma otwartej transakcji na bazie danych (w widoku sys.databases w bazie master jest kolumna log_reuse_wait_desc, możesz też użyć polecenia DBCC OPENTRAN).
  8. Liczba VLF-ów powinna być jak najmniejsza i nie powinna przekraczać 50 (ma to wpływ na wydajność operacji zapisu i zmniejszania pliku loga).
  9. Szalenie ważna dla utrzymania loga jest strategia backupowania loga. Wykonuj backupy loga, jeśli nie chcesz, by rósł on “w nieskończoność”.
  10. Zaraz po wykonaniu operacji zmniejszenia loga, zwiększ rozmiar pliku .ldf do rozmiaru właściwego. Wykonanie jednorazowego zwiększenia loga w miejsce wielu automatycznych operacji powiększenia spowoduje, że w logu będzie mniej VLF-ów. To dlatego wykonywanie operacji zmniejszania loga nierzadko mija się z celem. Czasem po prostu lepiej go opróżnić, skontrolować liczbę VLF-ów i – jeśli wszystko jest w porządku – zostawić nieco większy plik loga (i uniknąć jego “poszatkowania” przy operacjach automatycznego powiększania).

Ciekawostką może być fakt, że wiele razy próbowałem w tym procesie wykorzystać polecenie CHECKPOINT (celowo nie umieściłem go ani razu w kodzie, by nie psuć obrazu całości). Jednak nie zauważyłem istotnego wpływu CHECKPOINTa na VLF-y i problem zmniejszania pliku .ldf.

Dodatki

O poleceniu DBCC LOGINFO i innych nieudokumentowanych poleceniach DBCC poczytasz w artykule: SQL Server 2005 - nieudokumentowane polecenia DBCC.

Swego czasu o utrzymaniu loga bardzo mądrze i ciekawie napisała Kimberly L. Tripp: 8 Steps to better Transaction Log throughput.

Credits

W przeprowadzeniu pokazanego tu doświadczenia i wyciąganiu słusznych, jak się wydaje, wniosków, uczestniczyli Jarek Kuśmierek oraz Marek Adamczuk. Jarkowi składam osobne podziękowania za dążenie do prawdy w kwestii działania DBCC SHRINKFILE i obalenie mylnego przekonania, jakoby polecenie to mogło być wykonywane z pewnym naturalnym “odłożeniem” w czasie (deferred log shrink umarł ponoć wraz z SQL Server 7.0 - http://msdn.microsoft.com/en-us/library/aa902689(SQL.80).aspx).

Opublikowane 25 lipca 2008 09:03 przez brejk
Attachment(s): transaction_log.txt

Powiadamianie o komentarzach

Jeżeli chciałbyś otrzymywać email gdy ta wypowiedź zostanie zaktualizowana, to zarejestruj się tutaj

Subskrybuj komentarze za pomocą RSS

Komentarze:

Brak komentarzy

Co o tym myślisz?

(wymagane) 
wymagane 
(wymagane) 

  
Wprowadź kod: (wymagane)

About brejk

MVP, MCT, SQL Server geek