Eigene Tabellen & Hintergrundjobs

Akademie

Eigene Tabellen & Hintergrundjobs

dbDelta, $wpdb, WP-Cron und Transients.

Eigene Tabellen mit dbDelta und $wpdb

Begriffe vorab

  • $wpdb: die globale Datenbankklasse von WordPress.
  • Tabellenpräfix: $wpdb->prefix, in der Konfiguration festgelegt (nicht fest wp_).
  • dbDelta: Funktion, die Tabellen anlegt oder an eine neue Definition anpasst.
  • Prepared Statement: eine Abfrage mit Platzhaltern, die Werte sicher einsetzt.

Wann eine eigene Tabelle?

Für wenige, einfache Werte genügen Optionen und Post-Meta. Eine eigene Tabelle lohnt, wenn viele Datensätze mit eigenem Aufbau entstehen oder gezielt abgefragt werden: Merklisten vieler Nutzer, Statistikeinträge, Protokolle. Post-Meta bei Millionen Zeilen wird langsam, eine passend indexierte Tabelle nicht.

Tabelle mit dbDelta anlegen

dbDelta() vergleicht die gewünschte Definition mit der vorhandenen und legt an oder ändert. Das macht es wiederholbar – ideal für Updates. Es ist aber pingelig bei der Schreibweise:

  • Jedes Feld in einer eigenen Zeile.
  • Zwei Leerzeichen zwischen PRIMARY KEY und der Definition.
  • Das Wort KEY statt INDEX.
  • Keine Apostrophe oder Backticks um Feldnamen.
class MZ_Store {
    public static function table() {
        global $wpdb;
        return $wpdb->prefix . 'mz_items';
    }

    public static function create_table() {
        global $wpdb;
        $charset = $wpdb->get_charset_collate();
        $sql = "CREATE TABLE " . self::table() . " (
            id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
            user_id bigint(20) unsigned NOT NULL,
            post_id bigint(20) unsigned NOT NULL,
            created datetime NOT NULL,
            PRIMARY KEY  (id),
            UNIQUE KEY user_post (user_id,post_id),
            KEY post_id (post_id)
        ) $charset;";
        require_once ABSPATH . 'wp-admin/includes/upgrade.php';
        dbDelta( $sql );
    }

Der eindeutige Schlüssel user_post sorgt dafür, dass derselbe Beitrag pro Nutzer nur einmal gespeichert werden kann – die Datenbank erzwingt, was die Anwendung sonst prüfen müsste.

Sicher lesen und schreiben

    public static function add( $user_id, $post_id ) {
        global $wpdb;
        return $wpdb->insert(
            self::table(),
            array( 'user_id' => $user_id, 'post_id' => $post_id, 'created' => current_time( 'mysql', true ) ),
            array( '%d', '%d', '%s' )
        );
    }

    public static function for_user( $user_id, $limit = 50 ) {
        global $wpdb;
        return $wpdb->get_results( $wpdb->prepare(
            'SELECT post_id, created FROM ' . self::table() . ' WHERE user_id = %d ORDER BY created DESC LIMIT %d',
            $user_id, $limit
        ) );
    }
}

Der Tabellenname darf nicht über einen Platzhalter eingesetzt werden, Werte schon. Deshalb kommt der Name aus $wpdb->prefix und fester Zeichenfolge, alle Nutzerwerte laufen über %d/%s. Eine Verkettung von Nutzereingaben in SQL ist die klassische Einfallstür für SQL-Injection.

Zum Selbermachen

  1. Lege die Tabelle über die Aktivierung an und prüfe sie in einem Datenbankwerkzeug.
  2. Füge denselben Eintrag zweimal ein und beobachte, dass der eindeutige Schlüssel den Doppeleintrag verhindert.
  3. Ergänze eine Spalte, erhöhe die DB-Version und prüfe, dass dbDelta die Tabelle anpasst.

Prüfstatus: Belegt (Stand 1. Oktober 2026): Jahreszahlen, Zahlen, Namen und Quellenangaben dieser Lektion, soweit die Quellenliste sie nennt, wurden gegen Primärquellen geprüft. Nicht einzeln belegt: erklärende Darstellung nach Lehrbuchstand und Quellen, die in der Liste als „allgemeine Referenz“ markiert sind. Codebeispiele sind nur auf Syntax geprüft, nicht in WordPress ausgeführt.

Quellen

  • WordPress Plugin Handbook – „Creating Tables with Plugins“ (dbDelta-Regeln; developer.wordpress.org/plugins/creating-tables-with-plugins/) (allgemeine Referenz, nicht Zeile für Zeile geprüft)
  • WordPress Developer Resources – Klassenreferenz zu wpdb (insert, prepare, get_results, get_charset_collate) (allgemeine Referenz, nicht Zeile für Zeile geprüft)

Hintergrundjobs und Zwischenspeicher: WP-Cron und Transients

Begriffe vorab

  • WP-Cron: der in WordPress eingebaute Zeitplan für wiederkehrende Aufgaben.
  • Event: eine geplante Ausführung eines Hooks.
  • Intervall: der Abstand zwischen zwei Ausführungen (z. B. hourly, daily).
  • Transient: ein zwischengespeicherter Wert mit Ablaufzeit.
  • Object Cache: ein Zwischenspeicher im Arbeitsspeicher, der Abfragen beschleunigt.

Wie WP-Cron arbeitet

WP-Cron ist kein echter Cronjob des Servers. Bei jedem Seitenaufruf prüft WordPress, ob geplante Aufgaben fällig sind, und stößt sie im Hintergrund über wp-cron.php an. Daraus folgt: Auf einer Seite ohne Besucher laufen Aufgaben verspätet oder gar nicht, und bei sehr viel Verkehr entstehen viele unnötige Prüfungen. Auf stark genutzten Seiten schaltet man deshalb die Seitenaufruf-Auslösung mit define( 'DISABLE_WP_CRON', true ) ab und ruft wp-cron.php stattdessen per echtem Server-Cronjob in festen Abständen auf. Wer das Abschalten ohne Ersatz tut, bekommt gar keine Ausführung mehr.

Aufgabe planen – nur einmal

Der häufigste Fehler: wp_schedule_event() bei jedem Seitenaufruf aufrufen und so Hunderte gleiche Aufgaben anlegen. Plane stattdessen bei der Aktivierung und prüfe vorher mit wp_next_scheduled():

// Aktivierung
if ( ! wp_next_scheduled( 'mz_cleanup' ) ) {
    wp_schedule_event( time(), 'daily', 'mz_cleanup' );
}

// Die eigentliche Arbeit
add_action( 'mz_cleanup', function () {
    global $wpdb;
    $wpdb->query( $wpdb->prepare(
        'DELETE i FROM ' . MZ_Store::table() . ' i LEFT JOIN ' . $wpdb->posts . ' p ON p.ID = i.post_id WHERE p.ID IS NULL AND i.created < %s',
        gmdate( 'Y-m-d H:i:s', time() - DAY_IN_SECONDS )
    ) );
} );

// Deaktivierung
wp_clear_scheduled_hook( 'mz_cleanup' );

Die Aufgabe räumt Einträge zu inzwischen gelöschten Beiträgen weg. Eigene Intervalle ergänzt man über den Filter cron_schedules.

Transients als Zwischenspeicher

Teure Ergebnisse legt man mit einer Ablaufzeit ab, damit sie nicht bei jedem Aufruf neu berechnet werden:

function mz_popular_posts() {
    $cached = get_transient( 'mz_popular' );
    if ( false !== $cached ) {
        return $cached;
    }
    global $wpdb;
    $rows = $wpdb->get_results(
        'SELECT post_id, COUNT(*) AS n FROM ' . MZ_Store::table() . ' GROUP BY post_id ORDER BY n DESC LIMIT 10'
    );
    set_transient( 'mz_popular', $rows, 15 * MINUTE_IN_SECONDS );
    return $rows;
}

Ohne persistenten Object Cache liegen Transients in der Tabelle wp_options; mit einem Object Cache (z. B. Redis) liegen sie im Arbeitsspeicher. Ablaufzeiten sind Obergrenzen: Ein Transient kann früher verschwinden, deshalb muss der Code immer mit einem Fehlen rechnen.

Transients sind Zwischenspeicher, keine Datenbank. Alles, was nicht neu berechnet werden kann, gehört in Optionen oder eine Tabelle.

Zum Selbermachen

  1. Plane die Aufgabe bei Aktivierung und prüfe sie mit dem Plugin „WP Crontrol“ oder wp cron event list.
  2. Löse sie manuell aus und beobachte das Ergebnis.
  3. Messe die Laufzeit von mz_popular_posts() mit und ohne Transient.

Prüfstatus: Belegt (Stand 1. Oktober 2026): Jahreszahlen, Zahlen, Namen und Quellenangaben dieser Lektion, soweit die Quellenliste sie nennt, wurden gegen Primärquellen geprüft. Nicht einzeln belegt: erklärende Darstellung nach Lehrbuchstand und Quellen, die in der Liste als „allgemeine Referenz“ markiert sind. Codebeispiele sind nur auf Syntax geprüft, nicht in WordPress ausgeführt.

Quellen

  • WordPress Plugin Handbook – „Cron“ (developer.wordpress.org/plugins/cron/), einschließlich „Hooking WP-Cron Into the System Task Scheduler“ (allgemeine Referenz, nicht Zeile für Zeile geprüft)
  • WordPress Developer Resources – Funktionsreferenzen zu wp_schedule_event(), wp_next_scheduled(), set_transient() (allgemeine Referenz, nicht Zeile für Zeile geprüft)
  • WP-CLI Handbuch – „wp cron“ (developer.wordpress.org/cli/commands/cron/) (allgemeine Referenz, nicht Zeile für Zeile geprüft)