WP-CLI-Löschbefehle: Welche Tabellen jeder Befehl anfasst und was er zurücklässt

Inhalt
Datenbankpflege in WordPress ist eine Handvoll Befehle, die alle gleich aussehen und sich überhaupt nicht gleich verhalten. Einer geht durch die Anwendung und räumt hinter sich auf; der nächste fasst eine einzige Tabelle an und lässt vier weitere mit Zeilen zurück, die auf nichts mehr zeigen.
Wichtig wird der Unterschied dadurch, dass WP-CLI von wenigen Ausnahmen abgesehen kein Probelaufen kennt. Einen Löschbefehl zu fragen, was er täte, ist nicht vorgesehen. Es gibt nur die Übung, vorher einen anderen Befehl zu fragen.

Das Zweischrittmuster, das den Probelauf ersetzt
Zu jedem zerstörenden Befehl gibt es ein harmloses Geschwister, das denselben Filter annimmt. Zu wp post delete gehört wp post list, zu wp comment delete gehört wp comment list. Die Liste zuerst mit --format=count zu fahren, beantwortet die einzige Frage, die ein Probelauf beantwortet hätte: um wie viele Zeilen es gleich geht.
# 0. eine Sicherung, außerhalb des Web-Verzeichnisses
wp db export /home/pi/sicherung-$(date +%F).sql
# 1. zählen, mit genau dem Filter, der gleich verwendet wird
wp post list --post_type=revision --format=count
# 2. löschen, mit genau demselben Filter
wp post list --post_type=revision --format=ids | xargs -n 100 wp post delete --force
Das xargs in der letzten Zeile ist keine Zierde. Mehrere tausend Nummern unmittelbar in die Befehlszeile einzusetzen, läuft in die Längenbegrenzung der Argumente, und der Fehler kommt, nachdem ein Teil der Arbeit erledigt ist, statt bevor irgendetwas geschehen ist – also genau in der Lage, die das ganze Vorgehen vermeiden sollte.
Die Sicherungszeile enthält eine Feinheit, die im Gedächtnis bleiben sollte: Ohne Pfad schreibt wp db export den Abzug in das aktuelle Verzeichnis, und das ist bei einem WordPress-Befehl meist das Web-Verzeichnis. Ein vollständiger Datenbankabzug mit erratbarem Namen, über HTTP ausgeliefert, ist ein schlimmeres Ergebnis als alles, was das Aufräumen beheben sollte.
Zwei Wege, dieselbe Zeile zu entfernen
wp post delete ruft dieselbe Funktion auf wie der Editor. Der Befehl entfernt die Metadaten, die Kommentare, die Kommentarmeta und die Begriffsverknüpfungen, und er löst die Haken aus, über die Erweiterungen entfernen können, was sie anderswo angelegt haben – einen Sucheintrag, einen Zwischenspeicher, eine Zeile in ihrer eigenen Tabelle.
Ein unmittelbares DELETE FROM wp_posts entfernt eine Zeile. Alles, was auf sie verwies, bleibt liegen, unerreichbar und unsichtbar: Keine Abfrage verbindet sich mit einem Beitrag, den es nicht gibt, also tauchen die Zeilen schlicht nie wieder auf. Das ist tragbar, bis eine Nummer erneut vergeben wird – und Beitragsnummern werden nach einem Datenbankeinspielen erneut vergeben. Dann erbt der neue Beitrag lautlos die eigenen Felder und die Kommentare des alten.
Was jeder Befehl tatsächlich anfasst
| Befehl | Entfernt | Lässt stehen |
|---|---|---|
wp post delete <Nr> --force |
Beitrag, Metadaten, Kommentare, Begriffe | die angehängten Dateien und ihre Anhangszeilen |
wp post delete <Anhang> --force |
die Zeile und die Dateien auf der Platte, alle Größen | Verweise auf die Adresse in anderen Beiträgen |
wp post delete <Revisionen> |
die Revisionen und deren eigene Metazeilen | den Beitrag selbst, unangetastet |
wp comment delete --status=spam |
Kommentare und Kommentarmeta | die Kommentarzähler je Beitrag, bis zum Nachzählen |
wp transient delete --expired |
nur Transienten, deren Frist verstrichen ist | jeden Transienten, der ohne Frist gespeichert wurde |
wp db optimize |
nichts | eine Tabellensperre für die Dauer des Neuaufbaus |
Die erste Zeile überrascht am häufigsten. Einen Beitrag zu löschen, löscht die Bilder darin nicht – Anhänge sind eigene Beiträge mit eigenen Nummern und überleben ihren Erzeuger. Eine um tausend alte Beiträge erleichterte Seite trägt also weiterhin tausend Beiträge an Medien mit sich, und dort ist der Plattenplatz auch geblieben.
Die Zeile mit den Kommentarzählern ist am billigsten zu beheben und am leichtesten zu vergessen: Nach jedem größeren Kommentarlöschen bringt wp comment recount die Zahlen wieder mit den Zeilen in Übereinstimmung. Ohne das geht nichts kaputt; die Zähler sind bloß falsch, überall und lautlos.
Der Papierkorb ist keine Sicherung
Ohne --force verschiebt das Löschen einen Beitrag in den Papierkorb, und das ist eine Statusänderung und sonst nichts. Die Zeile bleibt, die Metadaten bleiben, die Medien bleiben, und Platz wird nicht frei – ein einmal im Monat geleerter Papierkorb ist eine Pflegegewohnheit und kein Sicherheitsnetz.
Mit --force überspringt der Beitrag den Papierkorb vollständig, und innerhalb von WordPress gibt es überhaupt kein Zurück. Zwischen einem vertippten Filter und einem verlorenen Monat steht allein der Abzug aus Schritt null – deshalb ist es Schritt null.
Abgelaufen ist nicht dasselbe wie alle
Transienten sind ein Zwischenspeicher mit einer neben dem Wert abgelegten Frist. wp transient delete --expired entfernt die, deren Zeit verstrichen ist, und ist von der Anlage her ungefährlich: Was gelöscht wird, war ohnehin ungültig.
--all ist ein anderer Befehl im selben Gewand. Er entfernt auch Transienten ohne Frist, und manche Erweiterungen verwenden genau die als einzigen Ablageort für etwas, dessen Berechnung teuer war – eine Lizenzprüfung, einen Bestand, ein fertig gebautes Menü. Sichtbar geht nichts kaputt; die Seite verbringt bloß die nächste Stunde damit, Dinge neu zu bauen, um die sie niemand gebeten hat, und ein oder zwei Erweiterungen benehmen sich, als wären sie gerade installiert worden.
Optimieren ist ein Neuaufbau, kein Aufräumen
wp db optimize führt OPTIMIZE TABLE aus, und bei InnoDB ist das keine Optimierung, sondern ein vollständiger Neuaufbau der Tabelle samt ihrer Indizes. Für die Dauer ist die Tabelle für Schreibzugriffe gesperrt, was bei einem kleinen Blog eine Sekunde dauert und bei einer großen wp_postmeta lange genug, dass Besucher es merken.
Es ist zugleich der Schritt, der am verlässlichsten nichts zurückgewinnt. Platz aus gelöschten Zeilen wird von InnoDB bereits für neue Zeilen wiederverwendet; die Datei auf der Platte schrumpft nur, wenn sehr viel gelöscht wurde und seitdem nichts geschrieben. Einmal nach einem großen Aufräumen zu einer ruhigen Stunde lohnt sich – in einem nächtlichen Cron aufgehoben zu sein lohnt sich nicht, und genau dort landet der Befehl für gewöhnlich.
Fragen und Antworten
Wie lassen sich Zeilen finden, die nach einem direkten DELETE auf keinen Beitrag mehr zeigen?
Mit einer Verbindung, die ausdrücklich nach dem Fehlen fragt. Für die Metadaten sieht der Zählschritt so aus:
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'
Nach demselben Muster lassen sich wp_comments über comment_post_ID und wp_term_relationships über object_id prüfen. Bei der letzten Tabelle ist Vorsicht nötig, weil object_id nicht nur auf Beiträge zeigt: Die Linkkategorien der alten Linkverwaltung verweisen auf wp_links, und manche Erweiterungen hängen Begriffe an Benutzer oder eigene Objekte. Die Prüfung ist dort auf die Taxonomien zu beschränken, die tatsächlich an Beiträgen hängen.
Gelöscht wird erst nach der Sicherung und mit genau derselben Bedingung, also mit DELETE pm FROM wp_postmeta pm LEFT JOIN wp_posts p ON p.ID = pm.post_id WHERE p.ID IS NULL. Das ist das Zweischrittmuster aus dem Artikel, nur in SQL. Beide Anweisungen lesen wp_postmeta vollständig; auf einer großen Installation laufen sie deshalb besser zu einer ruhigen Stunde. Das Präfix wp_ ist der Standard und kann in einer Installation anders lauten.
Lässt sich die Zahl der Revisionen dauerhaft begrenzen, statt sie immer wieder zu löschen?
Ja, mit der Konstante WP_POST_REVISIONS in der wp-config.php: Eine Zahl legt fest, wie viele Revisionen je Beitrag erhalten bleiben, false schaltet neue Revisionen ab. Die Grenze greift allerdings erst, wenn ein Beitrag das nächste Mal mit Änderungen gespeichert wird; erst dann entfernt WordPress die ältesten Revisionen über der Grenze. Den Bestand räumt die Konstante also nicht auf, und mit false bleibt er ganz unberührt – dafür ist weiterhin der einmalige Lauf aus dem Artikel nötig.
Was ändert ein persistenter Objektcache an wp transient delete?
Er verlegt die Transienten aus der Datenbank in den Cache. Mit einem persistenten Objektcache wie Redis oder Memcached landen neue Transienten nicht mehr in wp_options, und wp transient delete erreicht nur noch die Zeilen, die dort stehen geblieben sind, meist Reste aus der Zeit vor dem Cache. WP-CLI weist in diesem Fall mit einer Warnung darauf hin.
Im Cache selbst verfallen Transienten mit Frist von allein, weil die Frist dort als Ablaufzeit des Eintrags gilt. Alle auf einmal entfernt in der Regel nur wp cache flush, und das leert den ganzen Objektcache, nicht nur die Transienten. Die Folge ist dieselbe wie bei --all, nur breiter: Nichts bricht, aber die Seite baut eine Weile alles neu auf, und teilen sich mehrere Installationen denselben Cache, kann es je nach Einrichtung auch deren Einträge treffen.
Was ist bei einer Multisite-Installation anders?
Jede Website des Netzwerks hat eigene Tabellen mit eigenem Präfix, etwa wp_2_posts, und ein WP-CLI-Befehl ohne --url arbeitet nur auf der Hauptwebsite. Für das ganze Netzwerk muss deshalb jeder Schritt einmal je Website laufen, die Zählung eingeschlossen, etwa mit wp site list --field=url als Quelle für xargs. wp db export sichert dagegen ohne weitere Angaben die ganze Datenbank und damit alle Websites auf einmal.