Das Frontend von CSS-Tricks ist normalerweise ziemlich schnell, da die meisten Seiten gecacht (und bei Anforderung nicht dynamisch generiert) werden. Bis vor kurzem hatte die CSS-Tricks WordPress-Admin-Oberfläche jedoch nicht das gleiche Glück.
Insbesondere der Beitragsbearbeitungsbildschirm war langsam. Qualvoll langsam. Das Speichern eines Entwurfs dauerte mehrere Sekunden, was ausreicht, um Ihren Arbeitsfluss mitten beim Schreiben eines Artikels zu unterbrechen.
Aufbauend auf einem Tipp von Pete Sorensen, der eine Performance-Frage auf dem WordPress StackExchange gestellt hat (und so freundlich war, uns davon zu erzählen), habe ich mich daran gemacht herauszufinden, was die Beitragsbearbeitungsseite so langsam machte.
Laden des Werkzeugkastens
In unserem vorherigen Artikel über langsame WordPress-Abfragen finden und beheben haben wir eine Handvoll Tools überprüft, mit denen Sie langsame SQL-Abfragen identifizieren können. Um dieses spezielle Problem zu lösen, habe ich Debug Bar verwendet und sichergestellt, dass SAVEQUERIES aktiviert war.
Eingrenzung der Verlangsamung
Mit den verfolgten SQL-Abfragen habe ich die Debug-Bar-Oberfläche geöffnet (über den Button in der oberen rechten Admin-Leiste) und zum Reiter "Queries" gewechselt. Hier ist, was ich gesehen habe:

Hier stimmt etwas nicht! Jede Zeit über 250 Millisekunden in der Datenbank ist alarmierend; über 1,5 Sekunden für SQL-Abfragen bringen mir Tränen in die Augen.
Als Referenz hier die Abfragelast auf anderen Admin-Seiten:

Ich habe die Liste der Abfragen durchgesehen, um zu sehen, ob etwas offensichtlich falsch war. Ich fand einen Verdächtigen:

2.000+ Millisekunden für *eine einzige* Abfrage sind schlechte Nachrichten. Warum, WordPress, warum?
Was ist Ihre Funktion und was tut sie?
Ich brauchte Kontext für die Abfrage. Petes Sorensens Tipp gab mir eine gute Vorstellung davon, woher sie kam, aber es ist immer gut, zu überprüfen, ob man dasselbe Problem hat. Zeit, tiefer einzutauchen.
In diesem Fall ist sie in der Funktion meta_form enthalten, die sich in `wp-admin/includes/template.php` befindet. Gleich am Anfang dieser Funktion sehen wir unsere berüchtigte Abfrage:
$limit = apply_filters( 'postmeta_form_limit', 30 );
$sql = "SELECT meta_key
FROM $wpdb->postmeta
GROUP BY meta_key
HAVING meta_key NOT LIKE %s
ORDER BY meta_key
LIMIT %d";
$keys = $wpdb->get_col( $wpdb->prepare( $sql, $wpdb->esc_like( '_' ) . '%', $limit ) );
Die Variable $keys speichert die Ergebnisse unserer langsamen Abfrage. Weiter unten in der Funktion meta_form sehen wir, wo $keys verwendet werden:
foreach ( $keys as $key ) {
if ( is_protected_meta( $key, 'post' ) || ! current_user_can( 'add_post_meta', $post->ID, $key ) )
continue;
echo "\n<option value='" . esc_attr($key) . "'>" . esc_html($key) . "</option>";
}
Diese Schlüssel sind meta_keys in der Tabelle wp_postmeta und sie werden verwendet, um Optionen für ein <select> im Meta-Box "Benutzerdefinierte Felder" auf dem Beitragsbearbeitungsbildschirm zu befüllen.

Warum ist es so langsam?
Um eine bessere Vorstellung davon zu bekommen, warum diese Abfrage langsam ist, habe ich phpMyAdmin verwendet, um eine EXPLAIN-Abfrage für die langsame SQL-Abfrage auszuführen. EXPLAIN gibt uns Einblicke, wie MySQL eine Abfrage ausführt. Bei komplizierten Abfragen kann EXPLAIN helfen, langsame Punkte in Ihrem SQL zu finden – manchmal ist es eine langsame Unterabfrage oder eine ineffiziente Operation, die Ihre Abfrage beeinträchtigt.
Selbst bei einfachen Abfragen wie unserer obigen Meta-SQL gibt EXPLAIN immer noch einen kleinen Einblick, um zu verstehen, was vor sich geht.

Die wichtigen Teile, die für diese Abfrage betrachtet werden müssen, sind:
- Die Anzahl der Zeilen (über 1 Million)
- Die Spalte "Extra", die uns die mehrdeutige Phrase "Using filesort" liefert.
"Using filesort" (#2) ist tatsächlich eine große Sache, denn es bedeutet, dass die Zeilen jedes Mal sortiert werden, wenn diese Abfrage ausgeführt wird, was eine kostspielige Operation ist.
Was können wir also dagegen tun? Dank WordPress Hooks können wir den langsamen Code durch eigenen ersetzen!
Ersetzen der Meta-Box
Jedes Mal, wenn Sie Änderungen am WordPress-Kern vornehmen, sollten Sie prüfen, ob es Aktionen oder Filter gibt, in die Sie eingreifen können, um Ihre Änderungen vorzunehmen. Hooks sind eine nicht-intrusive Methode, um Änderungen an Code vorzunehmen, der Ihnen nicht gehört.
Leider finden Sie nicht immer einen Hook, um das zu tun, was Sie suchen – insbesondere in älteren Teilen von WordPress. Eine Suche in `wp-admin/includes/meta-boxes.php` und `wp-admin/includes/template.php` (wo sich unser langsamer Code befindet) ergab keine Aktionen oder Filter, in die wir uns einklinken könnten.
Wenn wir uns nicht in bestehenden Meta-Box-Code einklinken können, haben wir immer noch eine Option: Wir können die Meta-Box mit einer eigenen *ersetzen*.
Der erste Schritt ist, die Kern-Meta-Box zu entfernen und WordPress über unsere neue Meta-Box zu informieren. Dies können wir mit dem add_meta_boxes Hook tun:
function admin_speedup_remove_post_meta_box() {
global $post_type;
if ( is_admin() && post_type_supports( $post_type, 'custom-fields' ) ) {
remove_meta_box( 'postcustom', 'post', 'normal' );
add_meta_box( 'admin-speedup-postcustom', __('Admin Speedup Custom Fields'), 'admin_speedup_post_custom_meta_box', null, 'normal', 'core' );
}
}
add_action( 'add_meta_boxes', 'admin_speedup_remove_post_meta_box' );
Der Aufruf von remove_meta_box sagt WordPress: "Hey, kümmere dich nicht um diese 'postcustom'-Meta-Box. Wir kümmern uns darum."
Nachdem die Kern-Meta-Box entfernt wurde, sagt der Aufruf von add_meta_box WordPress, dass die Funktion admin_speedup_post_custom_meta_box zum Generieren des HTML für unsere neue Meta-Box verwendet werden soll.
admin_speedup_post_custom_meta_box ist im Grunde eine Kopie der Funktion post_custom_meta_box aus dem WordPress-Kern mit einigen Änderungen; am bemerkenswertesten ist, dass wir den Aufruf von meta_form durch unsere eigene benutzerdefinierte Funktion admin_speedup_meta_form ersetzen.
Große Warnung vor Copy + Paste: Codeblöcke durch *nahezu identischen* Code zu ersetzen, ist nicht ideal; wenn sich etwas im Originalcode ändert, müssen wir sicherstellen, dass wir unsere Version desselben Codes aktualisieren.
Aber in diesem Fall ist die Meta-Box "Benutzerdefinierte Felder":
- Wahrscheinlich wird sie sich nicht viel ändern.
- Keine kritische Funktion – selbst wenn sie bei der Aktualisierung von WordPress kurzzeitig ausfallen würde, würde die Website nicht zusammenbrechen.
Angesichts der minimalen Risiken und der Tatsache, dass wir über 2 Sekunden von der Seitenladezeit entfernen können, hielt ich es für vertretbar, die Kern-Meta-Box auszutauschen.
Schreiben des HTML der neuen Meta-Box
Nachdem wir uns vom moralischen Argument für den Austausch einer Kern-Meta-Box überzeugt haben, können wir die Funktionen schreiben, die unsere mutierte Version der "Benutzerdefinierten Felder"-Box anzeigen.
Zuerst definieren wir admin_speedup_post_custom_meta_box, die Funktion, die WordPress zum Schreiben des HTML für unsere neue Meta-Box verwendet:
function admin_speedup_post_custom_meta_box( $post ) {
?>
<div id="postcustom">
<div id="postcustomstuff">
<div id="ajax-response"></div>
<?php
$metadata = has_meta($post->ID);
foreach ( $metadata as $key => $value ) {
if ( is_protected_meta( $metadata[ $key ][ 'meta_key' ], 'post' ) || ! current_user_can( 'edit_post_meta', $post->ID, $metadata[ $key ][ 'meta_key' ] ) )
unset( $metadata[ $key ] );
}
list_meta( $metadata );
admin_speedup_meta_form( $post ); ?>
<p><?php _e('Custom fields can be used to add extra metadata to a post that you can use in your theme.'); ?></p>
</div>
</div>
<?php
}
Der Großteil des obigen Codes ist aus dem WordPress-Kern kopiert und eingefügt (Originalcode hier). Die 2 Unterschiede in unserem Code:
- Wir haben den Aufruf von
meta_formdurch unsere eigene Funktionadmin_speedup_meta_formersetzt, die wir gleich definieren werden. - Wir haben ein zusätzliches Wrapper-Div mit der ID
postcustomhinzugefügt – diese ID wird von einigen JavaScripts erwartet, die der Meta-Box AJAX-Funktionalität verleihen, und es ist viel einfacher, die Dinge so *nah wie möglich* am Original-Markup zu halten, um nichts kaputt zu machen.
Behebung der langsamen Abfrage
Endlich sind wir an dem Punkt, an dem wir diesen langsamen Störenfried beheben können. Die SQL für unsere meta_key SQL befindet sich in der Funktion meta_form in `wp-admin/includes/template.php`. Wir werden dies in unserer Funktion admin_speedup_meta_form lösen, die *im Grunde* dasselbe ist wie die ursprüngliche Funktion meta_form mit einer wichtigen Änderung.
Hier ist unser neuer Code:
function admin_speedup_meta_form( $post = null ) {
global $wpdb;
$post = get_post( $post );
if ( false === ( $keys = get_transient( 'admin_speedup_meta_keys' ) ) ) {
$limit = apply_filters( 'postmeta_form_limit', 30 );
$sql = "SELECT meta_key
FROM $wpdb->postmeta
GROUP BY meta_key
HAVING meta_key NOT LIKE %s
ORDER BY meta_key
LIMIT %d";
$keys = $wpdb->get_col( $wpdb->prepare( $sql, $wpdb->esc_like( '_' ) . '%', $limit ) );
set_transient( 'admin_speedup_meta_keys', $keys, 60 * 60 );
}
if ( $keys ) {
natcasesort( $keys );
$meta_key_input_id = 'metakeyselect';
} else {
$meta_key_input_id = 'metakeyinput';
}
?>
<p><?php _e( 'Add New Custom Field:' ) ?></p>
<?php // ...snip a bunch of duplicated code... ?>
</tbody>
</table>
<?php
}
Die "wichtigen Änderungen" sind die Aufrufe von get_transient und set_transient. Wenn Sie nicht wissen, was ein "Transient" ist, hat CSS-Tricks kürzlich einen hilfreichen Artikel veröffentlicht, der die Nuancen von WordPress Transients erklärt.
Für die Zwecke dieses Artikels sind Transients einfach eine Möglichkeit, die Ergebnisse eines aufwändigen Code-Teils zu speichern, damit sie später nicht neu generiert werden müssen. In diesem Fall speichern wir die Ergebnisse einer aufwändigen SQL-Abfrage mit dem Transient-Schlüssel admin_speedup_meta_keys. Wir prüfen zuerst, ob WordPress den Wert bereits für uns gespeichert hat, indem wir get_transient verwenden. Wenn ja, können wir die SQL-Abfrage überspringen und normal fortfahren. Wenn nicht, führen wir die langsame SQL-Abfrage aus und speichern die Ergebnisse für eine Stunde (60 * 60 Sekunden).
Mit dem Transients-Code in der Hand wird unsere langsame SQL-Abfrage nur etwa einmal pro Stunde ausgeführt – viel besser als bei jeder Anfrage!
Mit unserem Code zeigt die Abfrageüberwachung, dass die langsame Abfrage nicht bei jedem Seitenaufruf stattfindet:

Erfolg! Es bleibt nur noch ein Problem: Angenommen, jemand fügt einen neuen meta_key zur Liste hinzu. Unsere als Transient gespeicherte Liste wäre etwa 1 Stunde lang *ungültig*. Wir brauchen eine Möglichkeit, die gecachten Abfrageergebnisse zu löschen.
Hinzufügen des Buttons "Meta-Schlüssel löschen"
Es gibt nur zwei schwierige Dinge in der Informatik: Cache-Invalidierung und Benennung von Dingen.
— Phil Karlton
Quelle
Zu wissen, wann ein gecachter Wert ungültig ist, ist ein schwieriges Problem. Es gibt viele Möglichkeiten, wie die Liste der meta_keys geändert oder hinzugefügt werden kann, z. B. wenn Plugins automatisch Schlüssel hinzufügen oder Admin-Benutzer sie manuell eingeben.
Wenn ich auf ein schwieriges Problem stoße, versuche ich, die faulste Sache zu tun: Sie in Ruhe lassen.
Meta-Boxen werden nur im WP-Admin verwendet, und das Ergebnis dieser speziellen Abfrage ändert sich nicht oft. In diesem Fall habe ich beschlossen, einfach einen Button hinzuzufügen, um den gecachten (Transient) Wert zu löschen, damit ein Admin-Benutzer die Liste der meta_keys einfach neu generieren kann, wenn er feststellt, dass sie veraltet ist. Es ist nicht elegant, aber es funktioniert und ist einfacher, als zu versuchen, den Cache zu invalidieren.
Um den Button hinzuzufügen, benötigen wir 2 Codezeilen:
- HTML für den Button in der Meta-Box
- Code im Backend zum Löschen des Transient-Werts
Zuerst habe ich den Button zur neu erstellten Funktion admin_speedup_post_custom_meta_box hinzugefügt:
function admin_speedup_post_custom_meta_box($post) {
?>
<div id="postcustom">
<div id="postcustomstuff">
<div id="ajax-response"></div>
<?php
$metadata = has_meta($post->ID);
foreach ( $metadata as $key => $value ) {
if ( is_protected_meta( $metadata[ $key ][ 'meta_key' ], 'post' ) || ! current_user_can( 'edit_post_meta', $post->ID, $metadata[ $key ][ 'meta_key' ] ) )
unset( $metadata[ $key ] );
}
list_meta( $metadata );
admin_speedup_meta_form( $post ); ?>
<?php // Here's the new lines: ?>
<?php $current_url = add_query_arg( 'admin_speedup_refresh_meta_keys', '1' ); ?>
<div style="padding: 20px; margin: 20px 0; background: #CCC">
</div>
<p><?php _e('Custom fields can be used to add extra metadata to a post that you can use in your theme.'); ?></p>
</div>
</div>
<?php
}
Dieser Code fügt einen neuen Link zu unserer neuen Meta-Box hinzu, der wie folgt aussieht:

Fügen Sie nun Code hinzu, um das eigentliche Aktualisieren der Meta-Schlüssel zu handhaben:
function admin_speedup_clear_meta_keys() {
if ( is_admin() && isset( $_GET['admin_speedup_refresh_meta_keys'] ) && wp_verify_nonce( $_GET['_admin_speedup_nonce'], 'admin_speedup_refresh_meta_keys' ) ) {
delete_transient( 'admin_speedup_meta_keys' );
}
}
add_action( 'admin_init', 'admin_speedup_clear_meta_keys' );
Wenn nun auf den Link "Meta-Schlüssel aktualisieren" geklickt wird, geschieht Folgendes:
- Ein Nonce wird überprüft, um CSRF zu verhindern (mehr dazu hier).
- Wenn die Nonce-Prüfung erfolgreich ist, wird der Transient-Wert über
delete_transientgelöscht. - Nachdem der Transient gelöscht wurde, wird die (langsame) SQL-Abfrage erneut ausgeführt und die Liste der
meta_keysist aktuell.
Zusammenfassung dessen, was wir getan haben
Wir fanden ein langsames SQL, das in einer der Kern-Meta-Boxen von WordPress lebte. Nachdenken über unsere Optionen zur Behebung langsamer SQL-Abfragen entschieden wir uns, die Ergebnisse der Abfrage einfach zu cachen, um es einfach zu halten.
Dafür mussten wir die Kern-Meta-Box ausklinken und durch eine nahezu identische benutzerdefinierte Meta-Box ersetzen, die die Ergebnisse der langsamen Abfrage cach te. Schließlich haben wir, um zu verhindern, dass die gecachten Ergebnisse veraltet sind, eine Möglichkeit für Admin-Benutzer hinzugefügt, den Cache zu löschen.
Die Ergebnisse: 1,5 Sekunden weniger Ladezeit für die typische Beitragsbearbeitungsseite, was eine spürbare Verbesserung darstellt.
Da dies ein Problem von (lang laufenden) Installationen mit vielen benutzerdefinierten Metainformationen ist, ist es gut zu wissen, wenn es soweit kommt. Danke für diese Arbeit und Ihre Erklärung.
Würden Sie bitte eine Plugin aus Ihrem Code machen und es auf GitHub veröffentlichen?
Grüße!
Könnte das nicht mit AJAX gemacht werden, ähnlich wie die Taxonomie-Meta-Boxen aufgebaut sind?
In letzter Zeit habe ich das Gefühl, dass etwas verdammt falsch ist, wie WordPress mit Metadaten umgeht.
Ich meine, jedes Mal, wenn Sie einen regulären Beitrag ändern müssen (was *immer* vorkommt, wenn Sie WP für etwas anderes als Bloggen verwenden), müssen Sie alle Daten in die Postmeta-Tabelle eingeben.
Ergebnis ist, dass die Tabelle unglaublich aufgebläht wird, viel mehr als jede andere Tabelle, meiner Erfahrung nach.
Ich habe keine Beweise dafür und bin kein PHP/MySQL-Experte, aber für mich sieht es so aus, als könnte das auf lange Sicht die Leistung beeinträchtigen.
Es wäre toll, Meinungen von echten Experten zu hören. Ist das etwas Ähnliches wie ein Problem? Wie behandeln andere Plattformen (Drupal, Joomla etc.) Metadaten von Beiträgen?
P.S. Ich kann auch den Begriff "Tabellen" nicht ertragen – ich meine, *drei* Tabellen zur Verwaltung von Taxonomien? Da muss es einen besseren Weg geben.
Erstens, wie fügt das Abrufen aller Daten in der Meta-Tabelle eine Aufblähung der Tabelle hinzu?
Zweitens werden Taxonomien in einigen Versionen nur noch 2 Tabellen verwenden. Sie bereiten die Änderung in den letzten Versionen vor.
Es ist kein Wunder, dass diese Abfrage langsam ist. Sie führt eine GROUP BY / HAVING-Klausel durch, ohne dass GROUP BY benötigt wird. Vermutlich wird dies getan, um eindeutige Werte zu erhalten. Sie tun es jedoch auf die denkbar langsamste Weise.
Macht genau dasselbe und wird die Datenbank viel weniger belasten. Es ist auch eine einfachere Behebung, als Caching und Cache-Invalidierung etc. machen zu müssen.
Es wäre interessant, einen offiziellen WordPress-Entwickler zu hören, der erklärt, was diese Abfrage tut, ob dieser Vorschlag funktionieren würde.
Eine Alternative zum Button "Meta-Schlüssel aktualisieren" wäre, den Transient zu löschen, wenn ein Post-Meta-Feld aktualisiert oder gelöscht wird. In diesem Fall könnte der Transient länger als eine Stunde gespeichert werden.
Ich werde dies statt "Aktualisieren" verwenden! Danke :)
Unterstütze die Idee, eine Lösung als Plugin zu erstellen. Und wenn Sie die richtigen Hooks einbauen, dann könnte dieser Code vielleicht ins Core gelangen :)
Danke für den Artikel
Patrick
Sie können es noch einen Schritt weiter gehen, indem Sie TLC Transients hinzufügen: https://github.com/markjaquith/WP-TLC-Transients
Damit können Sie dem Transient sagen, dass er im Hintergrund aktualisiert werden soll. Auf diese Weise müssen Sie, wenn der Transient aktualisiert werden muss, nicht warten, wie lange es auch immer dauert. Es wird der aktuelle Transient angezeigt und beim nächsten Seitenaufruf wird der neue angezeigt (ohne lange Ladezeit).
Dies scheint etwas zu sein, von dem viele Seiten profitieren können. Haben Sie das WordPress-Team kontaktiert, um zu sehen, ob dies in das Core integriert werden könnte?
WordPress macht es einfach zu tun
Die Verwendung eines Transients löst das Problem nicht wirklich. Ich meine, Sie hätten die Abfrage einfach optimieren können, stattdessen erstellen Sie eine benutzerdefinierte Meta-Box.
Ich sehe hier ein paar echte Lösungen:
Optimieren Sie die Abfrage
Entfernen Sie die Abfrage und die Auswahlbox
Entfernen Sie die Meta-Box komplett. Wer braucht sie sowieso?
Siehe https://css-tricks.de/swapping-a-wordpress-core-meta-box-to-speed-up-editing/#comment-1595719 für eine mögliche optimierte Abfrage.
Ich stimme zu, die Box standardmäßig zu entfernen, da *die meisten* Leute, die Metadaten verwenden, dies über andere Plugins, wie z. B. ACF, tun werden.
Wenn Sie die Meta-Box "Benutzerdefinierte Felder" nicht benötigen, können Sie einfach dies in Ihre functions.php-Datei einfügen, um sie zu unterdrücken...
Außerdem gibt es hier ein Ticket, das Sie vielleicht verfolgen möchten...
https://core.trac.wordpress.org/ticket/24498
...denn sobald es gelöst ist, sollten diese Workarounds nicht mehr notwendig sein.
TL