Koncept współbieżności wielowersyjnej (MVCC, Multi-Version Concurrency Control) powstał jako alternatywa dla ścisłych blokad, aby zapewnić równoległą pracę dużej liczby transakcji. Było to ważne w celu zmniejszenia konfliktów i blokad podczas jednoczesnego dostępu do danych, co jest szczególnie krytyczne w systemach OLTP.
Tradycyjne podejścia do blokady (np. blokowanie na poziomie wiersza) mogą prowadzić do spowolnienia aplikacji przy wysokiej konkurencji. Zadaniem MVCC jest umożliwienie transakcjom odczytu spójnych migawkowych danych, nawet gdy równocześnie są wykonywane operacje zapisu, zapewniając w ten sposób izolację i równoczesny dostęp.
MVCC jest realizowane w popularnych bazach danych (PostgreSQL, Oracle, MySQL/InnoDB) poprzez przechowywanie historii wersji wierszy. Podczas odczytu każda transakcja widzi tylko te wiersze, które zostały zatwierdzone przed jej rozpoczęciem, a wstawienia/aktualizacje tworzą nowe wersje wierszy bez ich natychmiastowego usuwania.
Przykład zapytania (PostgreSQL):
BEGIN TRANSACTION; SELECT * FROM orders WHERE status = 'processing'; UPDATE orders SET status = 'completed' WHERE id = 42; COMMIT;
Dopóki transakcja nie jest zakończona — inni użytkownicy będą widzieć poprzednią wersję wiersza, a dopiero po zatwierdzeniu zmiany będą dostępne dla nowych transakcji.
Kluczowe cechy:
Czy MVCC może całkowicie wyeliminować wszystkie rodzaje blokad i konfliktów?
Nie, w MVCC i tak mogą wystąpić konflikty przy jednoczesnej aktualizacji tych samych wierszy — na przykład przy równoczesnych UPDATE występuje konflikt zatwierdzeń (conflict write-write), a baza danych generuje błąd lub cofa jedną z transakcji.
Kiedy stare wersje wierszy są usuwane w MVCC i czy może to prowadzić do wycieków pamięci?
W większości baz danych stare wersje wierszy są usuwane przez specjalne procesy (VACUUM w PostgreSQL). Jeśli nie uruchomi się tych procesów, baza "rozrasta się" i spada wydajność.
Czy "select for update" działa poprawnie w warunkach MVCC i dlaczego potrzebne jest blokowanie?
Tak, zapytania SELECT FOR UPDATE blokują wiersze, aby uniknąć konfliktów przy równoległych zmianach, w przeciwnym razie mogłyby wystąpić "zagubione aktualizacje".
Przykład:
BEGIN; SELECT * FROM products WHERE id = 123 FOR UPDATE; UPDATE products SET quantity = quantity - 1 WHERE id = 123; COMMIT;
W dużym sklepie internetowym zrealizowano schemat z częstymi aktualizacjami zamówień bez konfiguracji VACUUM. Po miesiącu baza wzrosła 10-krotnie, a zapytania zwolniły się wielokrotnie.
Zalety:
Wady:
Wprowadzono regularny autovacuum, zastosowano kontrolę konfliktów zapisów, izolacja na poziomie REPEATABLE READ tylko dla krytycznych zapytań.
Zalety:
Wady: