
PostgreSQL czy MySQL do JSON? Jeden indeks zmienia swobodę zapytań

Gdy aplikacja ma filtrować dokumenty po zmieniających się kluczach i zagnieżdżonych wartościach, większą swobodę daje PostgreSQL z JSONB. Jeden indeks GIN na kolumnie może obsłużyć wiele warunków zawierania, istnienia kluczy i wybrane warunki jsonpath, co opisuje dokumentacja PostgreSQL dotycząca JSONB. Zakres tej swobody zależy od operatora i klasy indeksu: GIN nie przyspiesza automatycznie każdej operacji na dokumencie.
Jeśli aplikacja filtruje stale po tych samych polach, MySQL może być równie sensownym wyborem architektonicznym. Dokumentacja typu JSON w MySQL wskazuje, że kolumny JSON nie indeksuje się bezpośrednio: indeks można założyć na kolumnę wygenerowaną z wybranej wartości, a InnoDB obsługuje też indeksy wielowartościowe dla tablic. Różnica między silnikami dotyczy więc przede wszystkim tego, ile przyszłych ścieżek wyszukiwania trzeba przewidzieć podczas projektowania indeksów.
Jak daleko sięga indeks GIN

Indeks GIN na całej kolumnie JSONB jest użyteczny, gdy filtry przechodzą między różnymi częściami dokumentu. W warunkowym katalogu produktów raz może to być cecha techniczna, a innym razem wartość w zagnieżdżonej tablicy. Jeżeli warunki są zapisane za pomocą obsługiwanych operatorów, te zapytania mogą korzystać z tego samego indeksu bez dodawania osobnego indeksu dla każdej nowej cechy.
Domyślna klasa jsonb_ops obsługuje zawieranie, sprawdzanie istnienia kluczy i wybrane predykaty jsonpath. jsonb_path_ops obsługuje mniej operatorów, ale zwykle tworzy mniejszy indeks i pozwala bardziej selektywnie wyszukiwać określoną zawartość. Nie obsługuje operatorów sprawdzających samo istnienie klucza, więc zamiana klasy indeksu może odebrać przyspieszenie zapytaniom, które wcześniej z niego korzystały.
Znaczenie ma także miejsce zastosowania operatora. Warunek sprawdzający istnienie klucza działa na poziomie wartości JSONB, do której go przyłożono; indeks całej kolumny nie obsłuży automatycznie takiego operatora zastosowanego dopiero do wydobytego fragmentu. Wtedy można użyć indeksu wyrażenia na tym fragmencie albo, jeśli odpowiada to potrzebnej semantyce, zapisać warunek jako zawieranie odpowiedniej zagnieżdżonej struktury. To konkretny przypadek, w którym swoboda jednego indeksu ma granicę.
Co MySQL zyskuje przy znanych ścieżkach
Indeks wybranej wartości pasuje do aplikacji, która stale pyta na przykład o status lub kategorię zapisaną pod ustaloną ścieżką. Kolumna wygenerowana wydobywa tę wartość z dokumentu JSON, a indeks daje optymalizatorowi klucz do wyszukania pasujących wierszy. Jeśli później ważnym filtrem stanie się inna ścieżka, trzeba osobno zdecydować o jej indeksowaniu. Indeks wielowartościowy pomaga wyszukiwać elementy wskazanej tablicy, lecz nie zastępuje indeksu obejmującego dowolne klucze dokumentu.
Sama obecność indeksu nie wystarcza, jeżeli zapytanie nie pasuje do indeksowanego wyrażenia. Opis optymalizatora MySQL wyjaśnia, że może on użyć indeksu kolumny wygenerowanej nawet wtedy, gdy zapytanie nie wymienia jej nazwy, pod warunkiem zgodności wyrażenia i typu wyniku. Przy tekstach wydobywanych z JSON istotne bywa użycie JSON_UNQUOTE w definicji kolumny, aby usunąć cudzysłowy z wartości. W praktyce oznacza to, że pozornie podobne filtry mogą otrzymać różne plany wykonania.
Co faktycznie pokazuje benchmark JSON

W benchmarku Leaper opublikowanym 12 marca 2026 roku zestawiono PostgreSQL 16.2 i MySQL 9.1 InnoDB na serwerze z procesorem AMD EPYC 7763, z ośmioma wątkami klientów. Część dotycząca JSON obejmowała milion wierszy z dokumentami wielkości około 500 bajtów. Autorzy podali medianę z pięciu przebiegów po rozgrzaniu pamięci podręcznej; są to wyniki opisanej konfiguracji, a nie stała cecha każdego wdrożenia.
W ekstrakcji pola PostgreSQL osiągnął 28 400 zapytań na sekundę wobec 18 200 dla MySQL, a w filtrowaniu po zagnieżdżonym kluczu 9 800 wobec 4 100. Dla agregacji JSON wyniki wyniosły odpowiednio 3 200 i 1 400 zapytań na sekundę. W wyszukiwaniu indeksowanym było to 42 600 wobec 12 800, przy czym porównano GIN po stronie PostgreSQL z indeksem wielowartościowym po stronie MySQL. Ten ostatni wynik dotyczy dwóch różnych mechanizmów i konkretnych zapytań; nie mówi, ile razy szybszy będzie dowolny filtr aplikacji.
Wyniki ekstrakcji warto czytać oddzielnie od wyników indeksowania. Oba silniki przechowują dane w wewnętrznych formatach pozwalających odczytywać JSON bez ponownego parsowania całego tekstu przy każdym dostępie. Przewaga w opublikowanym teście nie wynika więc po prostu z zestawienia formatu binarnego z tekstowym. Dla wyboru bazy ważniejsze jest to, które z mierzonych operacji rzeczywiście dominują w ruchu aplikacji.
Odczyty punktowe, agregacje i koszt zapisów
Jeżeli większość żądań pobiera wiersz przez zwykły indeks identyfikatora, elastyczność GIN może niewiele zmienić w czasie odpowiedzi. W tym samym zestawie testów MySQL uzyskał wyższy wynik prostych odczytów punktowych przy mniejszych zbiorach, a przy największym badanym zbiorze kolejność się odwróciła. Były to odczyty po indeksowanej kolumnie, nie wyszukiwanie wewnątrz JSON. Nie należy przenosić ich wyniku na filtr zagnieżdżonej wartości.
Agregacja stawia inne wymaganie niż znalezienie dokumentów spełniających warunek. Indeks może ograniczyć liczbę wierszy do odczytu, lecz nie wykona za silnik całego grupowania ani obliczeń na wydobytych wartościach. Jeżeli raporty regularnie grupują dane według jednego pola JSON, przewidywalność tego pola może być ważniejsza niż możliwość filtrowania po wielu rzadko używanych ścieżkach.
Przy częstych zapisach liczy się także zakres indeksu, który trzeba utrzymywać po zmianie danych. GIN na całym dokumencie przechowuje informacje o wielu jego elementach, podczas gdy indeks wybranego wyrażenia obejmuje węższy fragment; podobny wybór zakresu dotyczy indeksu kolumny wygenerowanej w MySQL. W PostgreSQL aktualizacja dokumentu JSONB wiąże się ponadto z blokadą całego wiersza. Jeśli obciążenie obejmuje częste zmiany dużych dokumentów, koszt zapisów i ryzyko współbieżnych aktualizacji należą do decyzji o modelu danych, nie tylko o silniku.
Który wybór odpowiada obciążeniu
Najpierw warto rozdzielić zmienność danych od zmienności pytań o dane. Dokumenty mogą mieć wiele opcjonalnych pól, ale jeśli aplikacja filtruje zawsze po tych samych dwóch wartościach, szeroki indeks nie musi przynosić korzyści. Odwrotna sytuacja występuje wtedy, gdy nowe cechy regularnie stają się warunkami wyszukiwania: konieczność wskazywania kolejnych indeksowanych ścieżek staje się częścią kosztu rozwoju aplikacji.
- Zmienne ścieżki filtrów: PostgreSQL JSONB daje więcej swobody dzięki GIN na dokumencie, o ile zapytania korzystają z operatorów obsługiwanych przez wybraną klasę indeksu.
- Stabilny zestaw pól: MySQL pozwala indeksować wartości wydobyte ze znanych ścieżek. Tu większe znaczenie ma zgodność wyrażeń w zapytaniach niż szeroki zakres indeksowania dokumentu.
- Głównie odczyty po identyfikatorze: o wyniku może decydować zwykły indeks kolumny, a różnica w filtrowaniu JSON będzie dotyczyć niewielkiej części ruchu.
- Dużo agregacji lub zapisów: potrzebne jest porównanie rzeczywistych zapytań, rozmiaru dokumentów i kosztu utrzymania wybranych indeksów. Opublikowane liczby opisują określone wersje, dane i konfigurację.
Wybór sprowadza się do tego, czy ważniejsze będą przyszłe, trudne dziś do przewidzenia filtry, czy sprawne obsługiwanie ustalonego zestawu pytań. W pierwszym przypadku zakres GIN jest mocnym argumentem za PostgreSQL. W drugim MySQL może zapewnić potrzebne indeksowanie bez obejmowania całego dokumentu, a ostateczną różnicę pokaże profil odczytów i zapisów aplikacji.
Przeczytaj także:
Powiązane artykuły


Pinecone czy pgvector? Filtry potrafią zmienić zwycięzcę

SQLite czy PostgreSQL? Jeden zapisujący proces zmienia wybór

Supabase czy Firebase? Darmowy start może ukryć koszt migracji

Mistral OCR czy Azure? Tańsza strona może oznaczać więcej kodu

Asana czy Trello? Dwie osoby zmieniają sens darmowego planu
Zapisz się do naszego newslettera
Otrzymuj najnowsze wiadomości o Web3, AI i kryptowalutach prosto na swoją skrzynkę.