MVCC

Mechanizm obsługi współbieżności przechowujący wiele wersji wierszy. Pozwala czytać dane bez blokowania zapisów i odwrotnie.

MVCC (Multi-Version Concurrency Control) to mechanizm zarządzania współbieżnością, w którym baza danych zamiast nadpisywać wiersz „w miejscu”, trzyma kilka jego wersji naraz. Dzięki temu czytanie nie blokuje zapisu, a zapis nie blokuje czytania — każda transakcja widzi spójną „migawkę” danych z konkretnego momentu, nawet jeśli ktoś obok właśnie te dane zmienia.

Jak to działa

Klasyczny model z blokadami zakłada, że jeśli ktoś czyta wiersz, to ktoś inny musi poczekać z jego modyfikacją (i odwrotnie). To prosty, ale wolny pomysł — przy dużym ruchu transakcje stoją w kolejce. MVCC odwraca logikę: każda zmiana tworzy nową wersję wiersza, a stara wersja żyje dalej, dopóki ktokolwiek może jej jeszcze potrzebować. Każda transakcja dostaje numer (albo timestamp) i na jego podstawie baza decyduje, którą wersję wiersza jej pokazać.

W praktyce daje Ci to izolację bez ciągłego zakładania blokad odczytu. Czytelnicy widzą świat „zamrożony” na starcie swojej transakcji (poziom izolacji REPEATABLE READ czy SNAPSHOT), a piszący robią swoje. Koszt? Stare wersje trzeba kiedyś posprzątać, bo same nie znikają.

Przykład z praktyki

PostgreSQL to sztandarowy przykład MVCC. Każdy wiersz ma ukryte kolumny xmin (ID transakcji, która go utworzyła) i xmax (ID transakcji, która go usunęła/zmieniła). UPDATE nie modyfikuje wiersza w miejscu — wstawia nową wersję i oznacza starą jako nieaktualną. Te martwe wersje („dead tuples”) sprząta proces VACUUM:

  • VACUUM verbose analyze users; — odzyskuje miejsce po martwych wersjach i odświeża statystyki.
  • SELECT xmin, xmax, * FROM users; — podejrzysz wersjonowanie na własne oczy.

MySQL z silnikiem InnoDB też używa MVCC, ale inaczej: stare wersje trzyma w undo logach, a nie obok aktywnych wierszy. Oracle robi podobnie przez segmenty undo.

Na co uważać

Najczęstszy mit: „MVCC = brak blokad”. Nieprawda. Dwie transakcje próbujące zmienić ten sam wiersz nadal się zderzą — przy SERIALIZABLE jedna dostanie błąd serializacji i będzie musiała powtórzyć operację. MVCC eliminuje konflikty czytelnik–pisarz, nie pisarz–pisarz.

Druga pułapka to table bloat: jeśli VACUUM nie nadąża (np. długo otwarta transakcja blokuje czyszczenie starych wersji), tabela puchnie, a wydajność leci w dół. Dlatego w Postgresie pilnuje się autovacuum i nie zostawia otwartych transakcji „na wieczność”.

Pojęcia powiązane

Warto znać przy okazji: poziomy izolacji transakcji (ACID), snapshot isolation, optymistyczne i pesymistyczne blokowanie, VACUUM i autovacuum, undo log, transaction ID wraparound.