LW IT Solutions
« Blog Overview /Wtyczki i triki WordPress / meta_query w WP_Query: jak każda klauzula staje...
This post in other languages:

meta_query w WP_Query: jak każda klauzula staje się złączeniem i ile to kosztuje

meta_query w WP_Query: jak każda klauzula staje się złączeniem i ile to kosztuje
Spis treści
  1. Co WP_Query buduje z tablicy
  2. Indeks, którego nie ma
  3. Klauzule kosztujące więcej niż pozostałe
  4. Gdzie ten filtr należy zamiast tego
  5. Mierzyć zamiast zgadywać
  6. Źródła

Strona filtrująca wpisy po trzech polach własnych to w PHP jedna tablica, a w SQL cztery złączenia. Tablica czyta się jak filtr; SQL czyta się jak tabela odwiedzana ponownie dla każdego warunku – bo dokładnie tym jest.

Godne uwagi nie jest to, że działa wolno; większość witryn nigdy nie urośnie na tyle, by to rzucało się w oczy. Godne uwagi jest to, że koszt pozostaje niewidoczny w kodzie, który go wywołuje, i że rośnie od dwóch rzeczy naraz: od liczby klauzul i od rozmiaru tabeli, w którą nikt nie zagląda.

Schody etapów zapytania: zapytanie podstawowe czyta 4800 wierszy, każde z trzech złączeń meta_query dokłada 192000 wierszy, ten sam filtr jako taksonomia czyta 5120
Ten sam zbiór wyników, cztery drogi do niego. Pasek zapytania podstawowego nie brakuje – ma trzy piksele szerokości.

Co WP_Query buduje z tablicy

Każda klauzula w meta_query dostaje własny alias na wp_postmeta i własny warunek złączenia. Dwie klauzule, dwa aliasy; pięć klauzul, pięć. Tabelę łączy się z samą sobą tyle razy, ile jest warunków, a każde z tych złączeń ciągnie za sobą pełny ciężar tabeli.

SELECT wp_posts.ID FROM wp_posts
  INNER JOIN wp_postmeta pm1 ON wp_posts.ID = pm1.post_id
  INNER JOIN wp_postmeta pm2 ON wp_posts.ID = pm2.post_id
  INNER JOIN wp_postmeta pm3 ON wp_posts.ID = pm3.post_id
WHERE post_type = 'post' AND post_status = 'publish'
  AND ( pm1.meta_key = '_region' AND pm1.meta_value = 'polnoc' )
  AND ( pm2.meta_key = '_poziom' AND pm2.meta_value = 'b' )
  AND ( pm3.meta_key = '_aktywny' AND pm3.meta_value = '1' )
ORDER BY wp_posts.post_date DESC

Nie ma w tym nic błędnego. To dosłowne tłumaczenie tego, o co poproszono, i baza odpowie poprawnie – po odczytaniu na średniej witrynie rzędu pół miliona wierszy, żeby zwrócić czterdzieści.

Indeks, którego nie ma

wp_postmeta niesie trzy indeksy: klucz główny na meta_id, jeden na post_id i jeden na pierwszych 191 znakach meta_key. Na meta_value nie ma żadnego i sensownie być nie może: kolumna jest typu LONGTEXT, a indeks na kolumnie tekstowej nieograniczonej długości to albo przedrostek, albo nic.

Każdą klauzulę obsługuje się więc w dwóch ruchach. Złączenie znajduje wiersze wpisu po post_id – szybko, z indeksu, dokładnie do tego indeks służy. Potem wartość porównuje się wewnątrz każdego z tych wierszy, jeden po drugim, bez pomocy skądkolwiek. Nakład jest proporcjonalny do liczby wierszy meta na wpis, a ta na witrynie z kreatorem stron i kilkoma rozszerzeniami rzadko spada poniżej trzydziestu.

Klauzule kosztujące więcej niż pozostałe

Klauzula Co musi za to zrobić baza
'compare' => '=' przypadek podstawowy: jedno złączenie, jedno porównanie napisów na wiersz
'compare' => 'LIKE' to samo z dopasowaniem wzorca; wiodący znak wieloznaczny wyklucza każdy skrót
'type' => 'NUMERIC' otacza kolumnę CAST() i zabiera ostatnią szansę na użycie indeksu
'compare' => 'NOT EXISTS' LEFT JOIN wraz z testem IS NULL – każdy niepasujący wiersz musi najpierw powstać
'relation' => 'OR' klauzule przestają się nawzajem zawężać; zbiór pośredni to suma
'orderby' => 'meta_value' tabela tymczasowa i sortowanie po kolumnie tekstowej

Ostatni wiersz zamienia wolną stronę w przekroczenie czasu. Sortowania po wartości meta nie da się obsłużyć z żadnego indeksu; baza buduje więc cały wynik pośredni i sortuje go – a ponieważ meta_value jest typu LONGTEXT, sortowanie to odbywa się na dysku, a nie w pamięci, gdy tylko zbiór urośnie.

NOT EXISTS zasługuje na osobne ostrzeżenie. Czyta się jak tanie przeciwieństwo EXISTS, a jest przeciwieństwem taniości: wykazanie nieobecności oznacza odwiedzenie każdego kandydata – i właśnie tego indeks skrócić nie potrafi.

Gdzie ten filtr należy zamiast tego

Wartość, po której się filtruje, nie jest własnością wpisu w tym sensie, w jakim jest nią podtytuł. Jest przyporządkowaniem, a dla przyporządkowań WordPress ma już strukturę z właściwymi indeksami: wp_term_relationships jest zaindeksowana na obu kolumnach, więc filtr po terminie to jedno złączenie obsłużone w całości z indeksów, całkiem bez porównywania tekstu.

Wynikająca stąd reguła jest krótka. To, po czym się filtruje albo sortuje, należy do taksonomii – albo, gdy wartości są naprawdę liczbowe i ciągłe, do kolumny własnej tabeli. To, co odczytuje się dopiero po znalezieniu wpisu, może zostać w metadanych, gdzie kosztuje dokładnie jeden dostęp po post_id i nic poza tym.

Mierzyć zamiast zgadywać

Zapytanie zbudowane przez WP_Query jest dostępne przed wykonaniem: w $query->request leży gotowy SQL, a przepuszczony przez EXPLAIN odpowiada na jedyne dwa pytania, które się liczą. Kolumna rows to oszacowanie, ile wierszy odczyta każdy krok; kolumna Extra nazywa kosztowne kształty wprost, a Using temporary; Using filesort razem są podpisem sortowania, które nie rośnie razem z witryną.

wp db query "EXPLAIN SELECT wp_posts.ID FROM wp_posts INNER JOIN ..."

id  select_type  table     type    key       rows    Extra
1   SIMPLE       wp_posts  ref     type_st.  4800    Using temporary; Using filesort
1   SIMPLE       pm1       ref     post_id     40    Using where
1   SIMPLE       pm2       ref     post_id     40    Using where
1   SIMPLE       pm3       ref     post_id     40    Using where

Trzy wiersze z Using where po dostępie przez post_id to dokładnie opisany wyżej obraz: indeks znalazł wiersze, warunek zastosowano potem. Liczba w rows, pomnożona przez złączenia, jest wielkością rosnącą razem z witryną – i warto ją znać tego dnia, w którym powstaje zapytanie, a nie tego, w którym strona przestaje się wczytywać.

Źródła

Lukas Wojcik

Lukas Wojcik

Systems architect and technology enthusiast specializing in scalable tracking solutions, GMP Stack (GA4 & GTM), and robust backend architectures. Advocate for clean code and privacy-first design.

Get in Touch

Briefly describe your project or inquiry for a tailored response. This site is protected by reCAPTCHA.

Napisanie komentarza

Adres e-mail nie jest publikowany. Pola obowiązkowe oznaczono gwiazdką.

ALL ARTICLES & CATEGORIES

CCTV

Śledź tę kategorię przez RSS

Cloud & AI

Śledź tę kategorię przez RSS

Data Privacy

Wszystkie artykuły w tej kategorii (12) Śledź tę kategorię przez RSS

Digital Analytics

Wszystkie artykuły w tej kategorii (46) Śledź tę kategorię przez RSS

Digital Marketing

Wszystkie artykuły w tej kategorii (25) Śledź tę kategorię przez RSS

IT & Networks

Wszystkie artykuły w tej kategorii (15) Śledź tę kategorię przez RSS

Raspberry Pi

Śledź tę kategorię przez RSS

Smart Home

Wszystkie artykuły w tej kategorii (14) Śledź tę kategorię przez RSS

Tworzenie stron internetowych

Śledź tę kategorię przez RSS

Wtyczki i triki WordPress

Śledź tę kategorię przez RSS