HomeMethod

Knowledge

Web VitalsTechniquesSnippetsAnswersImpactsBottlenecks

Auto-clean expired transients

  • WordPress

WordPress doesn’t proactively delete expired transients. They accumulate in wp_options as dead rows that bloat the table and slow down queries — especially the autoload query that runs on every page load. Sites without Redis or Memcached are most affected.

clean-expired-transients.php
<?php
/**
 * Auto-Clean Expired Transients
 *
 * WordPress does not proactively delete expired transients — they sit in
 * wp_options until something requests them. On sites without an object cache,
 * this leads to thousands of stale rows that slow down autoload queries.
 * This snippet schedules a daily cleanup via WP-Cron to purge them.
 *
 * Usage: Add to functions.php. Activates on next page load.
 *
 * @category wordpress
 * @language php

 *
 * @author Adame Dahmani https://adame.io
 * @support Support my family project: https://adame.io/fundraiser
 */
add_action( 'init', function () {
    if ( ! wp_next_scheduled( 'snippet_clean_transients' ) ) {
        wp_schedule_event( time(), 'daily', 'snippet_clean_transients' );
    }
} );

add_action( 'snippet_clean_transients', function () {
    global $wpdb;

    $wpdb->query(
        $wpdb->prepare(
            "DELETE a, b FROM {$wpdb->options} a
            INNER JOIN {$wpdb->options} b ON b.option_name = CONCAT('_transient_', SUBSTRING(a.option_name, 20))
            WHERE a.option_name LIKE %s
            AND a.option_value < %d",
            $wpdb->esc_like( '_transient_timeout_' ) . '%',
            time()
        )
    );
} );

This schedules a daily WP-Cron job that deletes expired transient pairs (the value row and its timeout row) in a single query. On a busy site with plugins that create many transients, this can clean up thousands of rows per day.

Support our family now