Strona głównaArtykułyMVCC: Jak Bazy Danych Pozwalają Czytelnikom i Autorom Pracy Bez Blokowania Się

MVCC: Jak Bazy Danych Pozwalają Czytelnikom i Autorom Pracy Bez Blokowania Się

Wyobraź sobie bazę danych, w której długotrwały raport może odczytywać tabelę przez kilka minut, a setki transakcji stale aktualizują te same wiersze, a żadna ze stron nie musi czekać na drugą. To jest obietnica Multi-Wersyjnej Kontroli Konkurencyjności, czyli MVCC, strategii współbieżności, która cicho napędza PostgreSQL, silnik InnoDB MySQL oraz Oracle i wiele innych nowoczesnych baz danych relacyjnych. Zamiast starego podejścia polegającego na blokowaniu wiersza tak, aby tylko jedna transakcja mogła go dotknąć naraz, MVCC utrzymuje wielokrotne, zdatne do użytku wersje każdego wiersza, dzięki czemu każda transakcja może znaleźć wersję odpowiadającą momentowi jej rozpoczęcia. W tym laboratorium możesz obserwować to działanie: czytelnicy pobierają spójne obrazy, autorzy tworzą nowe wersje, a proces czyszczenia, który ostatecznie musi usunąć pozostałości. Po zobaczeniu tego wizualnie, kompromisy związane z poziomami izolacji i procesami sprzątania nagle stają się bardziej zrozumiałe.

mysimulator teamZaktualizowano — czerwiec 2026≈ 8 min czytania▶ Otwórz symulację

Wykonania zamiast blokad

Tradycyjna kontrola współbieżności poprzez blokowanie traktuje wiersz bazy danych jak pojedyncze, współdzielone zasób: jeśli pisarz chce go zmienić, wszyscy inni, w tym czytelnicy, muszą czekać, aż pisarz skończy. To proste, ale niszczy przepustowość, gdy obciążenie łączy długie odczyty z częstymi zapisami. MVCC podejmuje fundamentalnie inny podbój. Zamiast chronić pojedynczą kopię wiersza za pomocą blokady, baza danych utrzymuje wiele wersji tego wiersza jednocześnie, każda z nich oznaczona informacją o tym, kiedy została utworzona i ostatecznie zastąpiona. Kiedy transakcja rozpoczyna się, skutecznie wykonuje mentalny skan, wycinek bazy danych taką, jaką była w tym momencie. W trakcie całej trwania tej transakcji każde uruchomione przez nią zapytanie konsultuje ten wycinek zamiast zmieniającego się na bieżąco stanu tabeli. Pisarz aktualizujący wiersz nie modyfikuje ani nie niszczy wersji, do której wskazuje wycinek czytelnika; po prostu tworzy nową wersję obok starej. Czytelnik nadal widzi swój spójny obraz świata, niezmieniony, nawet gdy podlegające mu dane zmieniają się w tle. To kluczowy skok koncepcyjny: kontrola współbieżności przenosi się z 'chronić jedną kopię' na 'utrzymać wystarczającą liczbę kopii, aby każdy mógł zobaczyć spójną'.

Dlaczego Czytania i Zapisów Bez Blokowania Są Ważne

Poważny efekt praktyczny MVCC jest ogromny: czytelnicy nigdy nie blokują pisarzy, a pisarze nigdy nie blokują czytelników. W systemie opartym wyłącznie na blokowaniu, pojedyncze wolne zapytanie analityczne może sparaliżować wszystkie aktualizacje próbujące dotrzeć do tych samych wierszy, a fala zapisów może wygłaszać raportowanie zapytań w czasie oczekiwania. MVCC całkowicie rozrywa tę zależność. Panel sterowania pytający miliony wierszy i usługa płatności aktualizująca kilka z nich może działać jednocześnie tej samej tabeli bez czekania na siebie nawzajem. To dokładnie dlatego MVCC stało się domyślnym projektem w nowoczesnych systemach relacyjnych: PostgreSQL używa go od najwcześniejszych wersji, silnik InnoDB MySQL polega na nim, Oracle zapoczątkował wiele z tych pomysłów dziesiątki lat temu i bazy danych takie jak SQL Server (w trybie izolacji snapshot) i CockroachDB wykorzystują wariacje tego samego zasady. Zysk w zakresie współbieżności nie jest małą optymalizacją; często jest to różnica między bazą danych, która wygodnie skaluje się pod zmiennym obciążeniem, a bazą danych, która zatrzymuje się, gdy napotka się konflikt między ruchem raportowania i transakcyjnym. Dwu-fazyowe blokowanie nadal istnieje i nadal gwarantuje poprawność, ale robi to kosztem przepustowości, której nowoczesne aplikacje po prostu nie mogą sobie pozwolić.

Oznaczanie Wersji Idempotentnymi Parametrów Transakcji

Technicznie, MVCC działa poprzez dołączanie metadanych prowadzących ewidencję do każdej wersji wiersza. Kiedy transakcja tworzy lub modyfikuje wiersz, nowa wersja jest oznaczona identyfikatorem transakcji (lub znacznikiem czasu), który ją wygenerował. Gdy ten wiersz zostanie później zaktualizowany lub usunięty, stara wersja nie zostaje natychmiast usunięta; zamiast tego otrzymuje identyfikator transakcji, która go zastąpiła, co w praktyce oznacza 'byłem ważny do momentu, kiedy ta transakcja mnie zastąpiła'. Każda wersja wiersza więc zawiera okno widoczności: znacznik utworzenia i, po odejściu, znacznik wygaśnięcia. Kiedy rozpoczyna się nową transakcję i tworzony jest jej snapshot, baza danych rejestruje, które transakcje zostały już zatwierdzone w tym momencie. Od tego momentu, za każdym razem gdy ta transakcja odczytuje wiersz, przegląda dostępne wersje i wybiera tę, której utworzeniem była dana transakcja, która wcześniej dokonała zatwierdzenia przed rozpoczęciem snapshotu, a której wygasłą transakcję (o ile taka istnieje) jeszcze nie dokonano. To proste prawo jest tym, co zapewnia spójność snapshotu: filtruje zmiany wprowadzone przez transakcje w trakcie realizacji lub które rozpoczęły się po utworzeniu snapshotu, jednocześnie ujawniając wszystko, co zostało bezpiecznie zatwierdzone wcześniej.

Zmartwione Wiersze i Kompromis z Pustym Przestrzeganiem

Utrzymywanie starych wersji wierszy zamiast nadpisywania ich bezpośrednio to właśnie sprawia, że działa MVCC, ale nie jest to bezkosztowo. Każda aktualizacja lub usunięcie pozostawia po sobie martwą wersję, którą niektóre punkty czasowe mogą nadal potrzebować, a gdy żaden aktywny transakcja już jej nie potrzebuje, staje się ona czystym marnotrawstwem. PostgreSQL nazywa te pozostałe wiersze, które nie są już widoczne nikomu, zmartwionymi wierszami; inne systemy używają podobnego języka dla tego samego pomysłu. Pozostawione bez kontroli, zmartwione wiersze gromadzą się w tabelach i indeksach, rozbudowując przestrzeń dyskową, spowalniając skanowania sekwencyjne i pogarszając wydajność indeksów, ponieważ baza danych nadal musi omijać wszystkie te martwe ciężary, aby znaleźć żywe dane. Rozwiązaniem jest proces czyszczenia w tle, znany w PostgreSQL jako vacuum, który okresowo skanuje tabele, identyfikuje wersje, które żadna transakcja nie może już potrzebować, i odzyskuje to miejsce do ponownego użycia. Jest to fundamentalny kompromis MVCC: kupuje się współbieżność poprzez odroczone czyszczenie, a jeśli vacuum pozostanie w tyle, niezależnie od błędnej konfiguracji, bardzo długotrwałych transakcji utrzymujących otwarte stare punkty czasowe lub ogromnego wolumenu pisania, nastąpią rozrost tabel i pogorszenie wydajności. Dostrajanie autovacuum jest rutynowym, nieuniknionym elementem zarządzania bazą danych MVCC w dużych skalach.

Obsługa Konfliktów Write-Write

MVCC elegancko rozwiązuje konflikt między czytelnikami a pisarzami, ale nie może zlikwidować konfliktów pomiędzy zapisywaniem, ponieważ dwie transakcje nie mogą obu wygrać, próbując jednocześnie zaktualizować tę samą linię w tym samym czasie. Bazy danych rozstrzygają ten konflikt na jeden z dwóch sposobów, w zależności od poziomu izolacji. Często stosowanym podejściem jest oczekiwanie na zakończenie drugiej transakcji: pierwsza transakcja, która dotyka linii, uzyskuje ekskluzywne zablokowanie tej konkretnej linii (znacznie szersze zablokowanie niż blokowanie wszystkich czytelników), a druga transakcja czeka, aż pierwsza się wywiąże lub cofnie, po czym kontynuuje działanie w oparciu o zaktualizowaną dane. Alternatywą, stosowaną przy bardziej restrykcyjnych poziomach izolacji, takich jak serializable lub snapshot isolation, jest wykrywanie konfliktu i przerwanie jednej z transakcji z błędem serializacji lub konfliktu, zmuszając aplikację do ponownego uruchomienia. Ten kompromis odzwierciedla klasyczny wybór między optymistycznym a pesymistycznym współbieżnym dostępem: czekanie utrzymuje obie transakcje w stanie aktywności, ale ryzykuje deadlocków i opóźnień, podczas gdy przerwanie zapewnia przewidywalną opóźnienie, ale przenosi logikę ponownego uruchamiania na aplikację. Którą strategię wybierze dana baza danych i przy jakim poziomie izolacji, jest jedną z najbardziej istotnych decyzji projektowych wpływających na jej zachowanie w obciążeniu współbieżnym z dużą ilością zapisów.

Frequently asked questions

Czy MVCC oznacza, że baza danych nigdy nie używa blokad?

Nie. MVCC eliminuje potrzebę blokad odczytowych i zapobiega blokowaniu pisarzy przez czytelników, ale nadal stosowane są blokady poziomu wiersza do seryjnego wykonywania współbieżnych zapisów tego samego wiersza, a także inne typy blokad (np. blokady DDL na poziomie tabeli) istnieją dla zmian strukturalnych.

Jakie jest różnice między MVCC a blokadą dwuetapową?

Blokada dwuetapowa wymaga od każdej transakcji uzyskania i utrzymywania blokad w danych, z jakimi się obchodzi, co oznacza, że czytelników i pisarzy można sobie wzajemnie blokować. MVCC zamiast tego zapewnia każdej transakcji spójny obraz zbudowany z wielu wersji wierszy, więc odczyty nigdy nie muszą czekać na zapisy.

Co to dokładnie jest martwy wiersz (dead tuple) w PostgreSQL?

Martwy wiersz to stara wersja wiersza pozostawiona po aktualizacji lub usunięciu, która nie jest już widoczna dla żadnego aktywnego lub przyszłego obrazu transakcji. Nadal zajmuje miejsce na dysku do czasu, aż proces `VACUUM` odzyskuje je.

Czy długotrwała transakcja może powodować problemy pod MVCC?

Tak. Długotrwała transakcja utrzymuje otwarty swój obraz, co zmusza bazę danych do przechowywania każdej wersji wiersza, której ten obraz mógłby nadal potrzebować. Może to opóźnić `VACUUM` i spowodować znaczne rozrost tabel i indeksów, jeśli będzie trwać przez długi czas.

Dlaczego MySQL InnoDB i PostgreSQL używają MVCC?

Obie były zaprojektowane w celu obsługi obciążeń o wysokiej współbieżności, gdzie zapytania raportowe i transakcyjne zapisy zachodzą jednocześnie. MVCC pozwala każdemu silnikowi dostarczyć spójne odczyty bez zwalniania pisarzy, co jest niezbędne dla tego typu obciążenia w skali.

Wypróbuj na żywo

Wszystko powyżej działa bezpośrednio w Twojej przeglądarce — otwórz MVCC: How Databases Let Readers and Writers Work Without Blocking Each Other i zmieniaj parametry podczas działania. Nic nie jest instalowane ani przesyłane na serwer, cały model działa w jednej karcie.

▶ Otwórz symulację MVCC: How Databases Let Readers and Writers Work Without Blocking Each Other

Co znalazłeś?

Dodaj kroki odtworzenia (opcjonalnie)