Polecenia usuwania w WP-CLI: których tabel dotyka każde z nich i co pozostawia

Spis treści
Pielęgnacja bazy w WordPressie to garść poleceń, które wyglądają jednakowo i zachowują się zupełnie inaczej. Jedno idzie przez aplikację i sprząta po sobie; kolejne dotyka jednej tabeli i zostawia cztery inne z wierszami, które nie wskazują już na nic.
Różnica staje się istotna dlatego, że WP-CLI poza nielicznymi wyjątkami nie zna przebiegu próbnego. Zapytanie polecenia kasującego, co by zrobiło, nie jest przewidziane. Jest tylko nawyk zapytania wcześniej innego polecenia.

Wzorzec dwóch kroków zastępujący przebieg próbny
Do każdego polecenia niszczącego istnieje nieszkodliwe rodzeństwo przyjmujące ten sam filtr. Do wp post delete należy wp post list, do wp comment delete należy wp comment list. Uruchomienie listy najpierw z --format=count odpowiada na jedyne pytanie, na które odpowiedziałby przebieg próbny: o ile wierszy zaraz chodzi.
# 0. kopia zapasowa, poza katalogiem serwowanym przez WWW
wp db export /home/pi/kopia-$(date +%F).sql
# 1. policzyć, dokładnie tym filtrem, który zaraz zostanie użyty
wp post list --post_type=revision --format=count
# 2. usunąć, dokładnie tym samym filtrem
wp post list --post_type=revision --format=ids | xargs -n 100 wp post delete --force
xargs w ostatnim wierszu nie jest ozdobą. Wstawienie kilku tysięcy numerów wprost do wiersza poleceń wpada w ograniczenie długości argumentów, a błąd przychodzi, gdy część pracy jest już zrobiona, zamiast zanim cokolwiek się wydarzy – czyli dokładnie w sytuacji, której całe postępowanie miało uniknąć.
Wiersz z kopią zawiera szczegół warty zapamiętania: bez ścieżki wp db export zapisuje zrzut do bieżącego katalogu, a przy poleceniu WordPressa jest nim zwykle katalog serwowany przez WWW. Pełny zrzut bazy o odgadywalnej nazwie, wydawany po HTTP, to wynik gorszy niż wszystko, co porządkowanie miało naprawić.
Dwie drogi do usunięcia tego samego wiersza
wp post delete wywołuje tę samą funkcję co edytor. Usuwa metadane, komentarze, meta komentarzy i powiązania terminów oraz uruchamia haki, przez które rozszerzenia mogą usunąć to, co założyły gdzie indziej – wpis w indeksie wyszukiwania, pamięć podręczną, wiersz we własnej tabeli.
Bezpośrednie DELETE FROM wp_posts usuwa jeden wiersz. Wszystko, co na niego wskazywało, zostaje – nieosiągalne i niewidoczne: żadne zapytanie nie łączy się z wpisem, którego nie ma, więc te wiersze po prostu nigdy się już nie pojawiają. Da się z tym żyć, dopóki numer nie zostanie przydzielony ponownie – a numery wpisów przydziela się ponownie po imporcie bazy. Wtedy nowy wpis bezgłośnie dziedziczy pola własne i komentarze starego.
Czego naprawdę dotyka każde polecenie
| Polecenie | Usuwa | Zostawia |
|---|---|---|
wp post delete <nr> --force |
wpis, metadane, komentarze, terminy | załączone pliki i ich wiersze załączników |
wp post delete <załącznik> --force |
wiersz i pliki na dysku, wszystkie rozmiary | odwołania do adresu w innych wpisach |
wp post delete <rewizje> |
rewizje i ich własne wiersze meta | sam wpis, nietknięty |
wp comment delete --status=spam |
komentarze i meta komentarzy | liczniki komentarzy przy wpisach, do przeliczenia |
wp transient delete --expired |
tylko transjenty, którym minął termin | każdy transjent zapisany bez terminu |
wp db optimize |
nic | blokadę tabeli na czas przebudowy |
Pierwszy wiersz zaskakuje najczęściej. Usunięcie wpisu nie usuwa zawartych w nim obrazów – załączniki są osobnymi wpisami z własnymi numerami i przeżywają swojego rodzica. Witryna odchudzona o tysiąc starych wpisów nadal niesie ze sobą tysiąc wpisów mediów, i tam właśnie zostało miejsce na dysku.
Wiersz z licznikami komentarzy najtaniej naprawić i najłatwiej zapomnieć: po każdym większym kasowaniu komentarzy wp comment recount przywraca zgodność liczb z wierszami. Bez tego nic się nie psuje; liczniki są po prostu błędne, wszędzie i bezgłośnie.
Kosz nie jest kopią zapasową
Bez --force kasowanie przenosi wpis do kosza, a to zmiana statusu i nic więcej. Wiersz zostaje, metadane zostają, media zostają, a miejsce się nie zwalnia – kosz opróżniany raz w miesiącu to nawyk porządkowy, a nie siatka bezpieczeństwa.
Z --force wpis pomija kosz całkowicie i wewnątrz WordPressa nie ma żadnego powrotu. Między pomylonym filtrem a straconym miesiącem stoi wyłącznie zrzut z kroku zerowego – i dlatego jest to krok zerowy.
Przeterminowane to nie to samo co wszystkie
Transjenty to pamięć podręczna z terminem zapisanym obok wartości. wp transient delete --expired usuwa te, którym czas minął, i z założenia jest bezpieczne: co usuwa, i tak było już nieważne.
--all to inne polecenie w tym samym przebraniu. Usuwa również transjenty bez terminu, a niektóre rozszerzenia używają właśnie ich jako jedynego miejsca przechowywania czegoś, czego wyliczenie było drogie – sprawdzenia licencji, stanu magazynu, gotowego menu. Widocznie nic się nie psuje; witryna spędza tylko następną godzinę na odbudowywaniu rzeczy, o które nikt jej nie prosił, a jedno czy dwa rozszerzenia zachowują się, jakby dopiero co je zainstalowano.
Optymalizacja to przebudowa, a nie sprzątanie
wp db optimize wykonuje OPTIMIZE TABLE, a przy InnoDB nie jest to optymalizacja, tylko pełna przebudowa tabeli wraz z indeksami. Na ten czas tabela jest zablokowana do zapisu, co przy małym blogu trwa sekundę, a przy dużej wp_postmeta dostatecznie długo, by odwiedzający to zauważyli.
To zarazem krok, który najpewniej niczego nie odzyskuje. Miejsce po usuniętych wierszach InnoDB wykorzystuje ponownie dla nowych; plik na dysku kurczy się tylko wtedy, gdy usunięto bardzo dużo i od tego czasu nic nie zapisano. Raz po dużym porządkowaniu, o spokojnej godzinie – warto; trzymanie tego w nocnym cronie – nie warto, a właśnie tam polecenie zwykle ląduje.
Pytania i odpowiedzi
Jak znaleźć wiersze, które po bezpośrednim DELETE nie wskazują już na żaden wpis?
Złączeniem, które wprost pyta o brak. Dla metadanych krok zliczania wygląda tak:
wp db query 'SELECT COUNT(*) FROM wp_postmeta pm LEFT JOIN wp_posts p ON p.ID = pm.post_id WHERE p.ID IS NULL'
Według tego samego wzorca da się sprawdzić wp_comments po comment_post_ID i wp_term_relationships po object_id. Przy ostatniej tabeli potrzebna jest ostrożność, bo object_id nie wskazuje wyłącznie na wpisy: kategorie linków ze starego menedżera linków odwołują się do wp_links, a niektóre rozszerzenia przypisują terminy użytkownikom albo własnym obiektom. Tam sprawdzenie należy ograniczyć do taksonomii, które rzeczywiście są przypięte do wpisów.
Usuwanie następuje dopiero po wykonaniu kopii i z dokładnie tym samym warunkiem, czyli jako DELETE pm FROM wp_postmeta pm LEFT JOIN wp_posts p ON p.ID = pm.post_id WHERE p.ID IS NULL. To wzorzec dwóch kroków z artykułu, tyle że w SQL. Obie instrukcje czytają całą tabelę wp_postmeta, więc przy dużej instalacji lepiej uruchamiać je o spokojnej godzinie. Przedrostek wp_ jest domyślny i w konkretnej instalacji może brzmieć inaczej.
Czy liczbę rewizji da się trwale ograniczyć, zamiast raz po raz je usuwać?
Tak, stałą WP_POST_REVISIONS w wp-config.php: liczba określa, ile rewizji jednego wpisu jest przechowywanych, a false wyłącza tworzenie nowych. Limit zaczyna jednak działać dopiero przy następnym zapisie wpisu ze zmianami; dopiero wtedy WordPress usuwa najstarsze rewizje ponad limit. Istniejących rewizji stała więc nie porządkuje, a przy false w ogóle ich nie rusza – do tego nadal potrzebny jest jednorazowy przebieg z artykułu.
Co zmienia trwała pamięć podręczna obiektów w działaniu wp transient delete?
Przenosi transjenty z bazy danych do pamięci podręcznej. Przy trwałej pamięci podręcznej obiektów, takiej jak Redis czy Memcached, nowe transjenty nie trafiają już do wp_options, a wp transient delete sięga tylko do wierszy, które tam pozostały, zwykle resztek z czasu sprzed jej włączenia. WP-CLI sygnalizuje to ostrzeżeniem.
W samej pamięci podręcznej transjenty z terminem wygasają same, bo termin staje się tam czasem życia zapisanej pozycji. Wszystkie naraz usuwa z reguły tylko wp cache flush, a to polecenie opróżnia całą pamięć podręczną obiektów, nie tylko transjenty. Skutek jest taki sam jak przy --all, tylko szerszy: nic się nie psuje, ale witryna przez pewien czas wszystko odbudowuje, a jeśli kilka instalacji dzieli tę samą pamięć podręczną, zależnie od konfiguracji może to dotknąć także ich danych.
Co się zmienia w instalacji Multisite?
Każda witryna w sieci ma własne tabele z własnym przedrostkiem, na przykład wp_2_posts, a polecenie WP-CLI bez --url działa tylko na witrynie głównej. Dla całej sieci każdy krok, łącznie ze zliczaniem, należy więc wykonać raz dla każdej witryny, na przykład z wp site list --field=url jako źródłem dla xargs. wp db export zapisuje natomiast bez dodatkowych opcji całą bazę, a więc wszystkie witryny naraz.