PageSpeed Matters
    Speed Audit
    Let's Talk
    PageSpeed Matters
    Book a Call
    WordPress Guide

    WordPress Database Optimization (2026)

    Matt SuffolettoWritten by Matt Suffoletto
    Published June 23, 2026 13 min read
    Share

    A bloated WordPress database is one of the most common reasons a site that used to feel fast no longer does. Pages take 800 to 1,500 milliseconds before the first byte arrives, the admin lags when saving a post, and WP-Cron tasks pile up while MySQL works through millions of rows of stale data. Caching hides the problem for anonymous visitors, but the moment someone logs in, adds an item to a cart, or hits an uncached URL, the slow database is back in charge.

    This guide walks through the parts of the database that actually cause speed problems in 2026: autoloaded options, post revisions, transients, orphaned metadata, and table fragmentation. Each section includes a diagnostic query and a fix you can run safely with a backup. If you are still narrowing down whether the database is the bottleneck or something else, start with why is WordPress so slow; for the full performance stack from hosting to assets, the parent Ultimate Guide to WordPress Speed Optimization covers the rest.

    TL;DR

    Before doing anything: take a full database backup (UpdraftPlus, BlogVault, or a host snapshot). The single highest-impact fix is shrinking the autoloaded options row set: anything over 1MB of autoloaded data is hurting every single request, and most bloated sites are sitting at 5 to 50MB. Next, cap post revisions in `wp-config.php` and delete the historical ones with SQL. Then clear expired transients, remove orphaned post and user meta, and run OPTIMIZE TABLE on InnoDB tables that have seen heavy delete activity. Automate the recurring cleanup with WP-Optimize or Advanced Database Cleaner so the problem does not return in 90 days. Expect TTFB improvements of 100 to 400ms on uncached requests after a thorough cleanup, with the largest gains on sites that have been running for 5 or more years on the same database.

    Key Takeaways

    • Autoloaded options over 1MB are the number one hidden cause of slow WordPress backends.
    • Every uncached page load runs a query to fetch all autoloaded options, so bloat hits TTFB directly.
    • Cap post revisions in `wp-config.php` before cleaning, otherwise the bloat returns within months.
    • Expired transients are not auto-purged on most hosts and accumulate by the tens of thousands.
    • Orphaned post meta and user meta from uninstalled plugins can silently keep millions of rows alive.
    • OPTIMIZE TABLE reclaims disk and rebuilds indexes after heavy deletes; run it after cleanup, not before.
    • Automate recurring cleanup with WP-Optimize or Advanced Database Cleaner to stop the bloat returning.

    Why Database Bloat Slows WordPress

    Every request to an uncached WordPress page starts with the same sequence: PHP boots, WordPress loads, the connection to MySQL opens, and the framework asks for all autoloaded options in a single query. Then plugins and the theme run, firing dozens to hundreds of additional queries against `wp_posts`, `wp_postmeta`, `wp_options`, `wp_users`, `wp_usermeta`, and any custom tables the plugins added. The total time spent inside MySQL plus the time PHP spends processing those results is your server response time, or TTFB.

    When the database is small and clean, the autoload query returns 200 to 800KB of data in 10 to 30 milliseconds. When it is bloated, that same query can return 20MB and take 300 to 800 milliseconds, and that cost is paid on every uncached request. Cache plugins hide it for anonymous browsing, but logged-in users, the wp-admin dashboard, cart and checkout pages, search results, and any AJAX endpoint are all served uncached. A slow database makes the admin feel sluggish for editors and turns the customer experience for logged-in shoppers into a measurable revenue problem.

    Database bloat compounds with hosting limits. Shared MySQL plans cap simultaneous connections and queries per second, and a single bloated autoload query can hold a connection long enough to queue other requests behind it. On a managed host with a strong cache layer this stays invisible until traffic spikes; on a budget host it shows up as random 500 errors and slow admin saves. If hosting is also on the wishlist, our fastest WordPress hosting guide compares the managed tiers on cached and uncached TTFB.

    Autoloaded Options: The Number One Hidden Killer

    The `wp_options` table stores site-wide settings as key/value rows. Each row has an `autoload` flag. On every uncached request, WordPress runs roughly this query to populate the options cache:

    SELECT option_name, option_value FROM wp_options WHERE autoload = 'yes';

    Well-behaved installs have 200KB to 800KB of autoloaded data. Bloated ones have 5MB, 20MB, sometimes 100MB, usually because plugins (especially analytics, backup, security and ecommerce plugins) write large blobs into `wp_options` with `autoload = 'yes'` and never clean them up after they are uninstalled.

    Diagnose first. Run this in phpMyAdmin, Adminer, or the WordPress site health screen's database tab:

    SELECT option_name, LENGTH(option_value) AS size_bytes
    FROM wp_options
    WHERE autoload = 'yes'
    ORDER BY size_bytes DESC
    LIMIT 20;

    This returns the 20 largest autoloaded options. Anything over 1MB (1,048,576 bytes) is a problem. Common offenders: leftover settings from Yoast, RankMath, MonsterInsights, Wordfence transient blobs, abandoned email-marketing plugin queues, and ecommerce session debris.

    The fix has two parts. For options you recognize as belonging to an uninstalled plugin, delete them outright:

    DELETE FROM wp_options WHERE option_name = 'some_orphaned_option_name';

    For options that still belong to active plugins but do not need to load on every page, flip the autoload flag to `no` instead of deleting (the plugin will still find the value when it actively queries for it, the value just will not preload on every request):

    UPDATE wp_options SET autoload = 'no' WHERE option_name = 'some_large_active_option';

    Never blindly delete or flip rows you do not recognize: some plugins genuinely need their option preloaded. Test on a staging copy first, take a backup, and verify the site still loads after each change.

    Post Revisions: Cap First, Then Clean

    By default, WordPress stores an unlimited number of post revisions. Edit a post 40 times and you have 40 extra rows in `wp_posts`, each with their own metadata in `wp_postmeta`. On a content-heavy site running for years, revisions often outnumber the actual published posts by 10 to 50 times.

    Revisions do not directly slow down anonymous front-end requests (the queries for published content explicitly exclude `post_status = 'inherit'`), but they bloat the database file size, slow down backups, slow the editor when loading revision history, and inflate the row count that MySQL's query planner has to consider on certain joins.

    Cap the limit first, in `wp-config.php`, above the `/ That's all, stop editing! /` line:

    define('WP_POST_REVISIONS', 5);
    define('AUTOSAVE_INTERVAL', 120); // seconds between autosaves

    `WP_POST_REVISIONS` set to 5 keeps the last 5 revisions per post, which is enough to undo recent mistakes without storing years of history. `AUTOSAVE_INTERVAL` set to 120 reduces the rate at which the autosave system writes new revisions.

    Now clean the historical ones. This SQL deletes every revision and its associated postmeta and term relationships in one transaction:

    DELETE a, b, c
    FROM wp_posts a
    LEFT JOIN wp_term_relationships b ON (a.ID = b.object_id)
    LEFT JOIN wp_postmeta c ON (a.ID = c.post_id)
    WHERE a.post_type = 'revision';

    On a site with 50,000 revisions this typically reclaims 30 to 200MB and a few hundred thousand rows. Run it during a low-traffic window with a fresh backup. If you prefer not to run raw SQL, WP-Optimize and Advanced Database Cleaner both expose a one-click revision purge that runs an equivalent query.

    Transients and Expired Data

    Transients are short-lived cached values stored in `wp_options` (or, on sites with a persistent object cache, in Redis or Memcached). Plugins use them to cache the results of expensive operations: a feed fetch, a license check, an API response. Each transient has a paired `_transient_timeout_{name}` row that stores its expiry timestamp.

    The problem: WordPress only deletes a transient when something tries to read it after expiry. If the plugin that wrote it is uninstalled, or the code path that reads it is never hit again, the transient and its timeout row sit in the database forever. A 5-year-old site that has cycled through 30 plugins commonly has 10,000 to 100,000 expired transient rows.

    Diagnose how many you have:

    SELECT COUNT(*) FROM wp_options
    WHERE option_name LIKE '\_transient\_%'
    OR option_name LIKE '\_site\_transient\_%';

    Delete expired transients with this safe query (it only removes ones whose timeout has already passed):

    DELETE a, b FROM wp_options a, wp_options b
    WHERE a.option_name LIKE '\_transient\_%'
    AND a.option_name NOT LIKE '\_transient\_timeout\_%'
    AND b.option_name = CONCAT('_transient_timeout_', SUBSTRING(a.option_name, 12))
    AND b.option_value < UNIX_TIMESTAMP();

    For a more aggressive purge that removes every transient regardless of expiry (forcing plugins to regenerate them on next use, which is usually fine), WP-CLI has the cleanest path:

    wp transient delete --all

    Do this on a maintenance window for ecommerce sites: clearing transients can briefly increase load while plugins rebuild their caches.

    Orphaned Metadata

    Orphaned metadata is the long tail of database bloat: postmeta rows whose post no longer exists, usermeta rows whose user no longer exists, commentmeta rows whose comment is gone, and term meta whose term was deleted. WordPress's deletion routines try to clean these up, but plugin code that bypasses the standard delete API leaves orphans behind, and bulk operations (especially product imports, user imports, and form submissions stored as custom post types) frequently strand thousands of rows.

    A site with 50,000 products that ran two failed bulk imports can easily have 2 to 5 million orphaned postmeta rows, each of which slows down any query that scans postmeta.

    Find orphaned post metadata:

    SELECT COUNT(*) FROM wp_postmeta pm
    LEFT JOIN wp_posts p ON p.ID = pm.post_id
    WHERE p.ID IS NULL;

    Delete it:

    DELETE pm FROM wp_postmeta pm
    LEFT JOIN wp_posts p ON p.ID = pm.post_id
    WHERE p.ID IS NULL;

    Do the same for usermeta and commentmeta, substituting the right tables. The delete can take a few minutes on a large site, so run it off-hours. After it completes, MySQL will not immediately reclaim the disk space because InnoDB does not shrink data files automatically; the next section covers that.

    Table Optimization and Fragmentation

    After heavy DELETE activity, InnoDB tables (the default WordPress storage engine since 2014) hold internal free space that is reusable for new INSERTs but does not shrink the on-disk file. This is not slow by itself, but it inflates backup size and, after enough churn, can fragment the data pages enough to slow scans.

    Rebuild a table cleanly with:

    OPTIMIZE TABLE wp_options;
    OPTIMIZE TABLE wp_posts;
    OPTIMIZE TABLE wp_postmeta;
    OPTIMIZE TABLE wp_comments;
    OPTIMIZE TABLE wp_commentmeta;
    OPTIMIZE TABLE wp_users;
    OPTIMIZE TABLE wp_usermeta;

    On InnoDB, OPTIMIZE TABLE is internally mapped to `ALTER TABLE ... FORCE`, which rebuilds the table and its indexes. This locks the table for the duration of the rebuild (a few seconds on small tables, a few minutes on a 5GB `wp_postmeta`), so run it during low traffic. WP-CLI offers the safer batch version:

    wp db optimize

    Run OPTIMIZE TABLE after the cleanup, not before. Doing it on a bloated table just rebuilds the bloat.

    Automating Cleanup with WP-Optimize and Advanced Database Cleaner

    Manual cleanup once a year does not solve the problem. Revisions, transients and orphans regenerate continuously. The fix is to schedule the cleanup so it runs without anyone remembering.

    WP-Optimize is the most popular choice and free for the cleanup features. Install it, run a one-time cleanup with all options checked (revisions, autodrafts, trashed posts, spam comments, expired transients, pingbacks, trackbacks), then set the scheduled cleanup to weekly. The paid version adds image compression and a database backup integration, neither of which is required for cleanup itself.

    Advanced Database Cleaner is the better pick if you want surgical control over orphaned data, scheduled tasks (`wp_cron` rows that no longer have a matching plugin), and custom tables left behind by uninstalled plugins. The free version handles the basics; the Pro version exposes orphaned options, orphaned tables, and a useful 'show only orphan items' filter that no other tool matches.

    For sites with WP-CLI access, the leanest automation is a cron job running `wp transient delete --expired && wp db optimize` weekly. No plugin required, no UI overhead, no risk of plugin abandonment.

    Whichever path you pick, schedule the recurring cleanup once and verify it ran by checking the database size monthly. On WooCommerce stores the bloat builds 5 to 10 times faster than on blog-style sites because of order metadata, session rows and abandoned cart data, so weekly cleanup is the floor, not the ceiling. On builder-heavy sites the editor itself drives extra revisions and autosaves; see WordPress page builder speed for the matching builder-side fixes.

    If the database is one symptom of a deeper performance problem and you would rather not run SQL yourself, the WordPress speed optimization service handles autoload audits, revision policy, transient hygiene and OPTIMIZE TABLE as part of every engagement, alongside hosting, caching and image work.

    Frequently Asked Questions

    How big should the WordPress autoloaded options data be?

    Under 1MB total is healthy. 1 to 3MB is acceptable but worth investigating. Over 3MB measurably slows TTFB on every uncached request, and over 10MB will produce visible admin lag and frequent slow query log entries. Use the diagnostic query in the autoload section to find which rows are the largest.

    Will deleting post revisions break anything?

    No. Revisions are stored as separate rows with `post_type = 'revision'` and are never served on the front end. Deleting them removes the ability to roll back to an older draft, which is why you also cap `WP_POST_REVISIONS` so the most recent 5 are retained going forward.

    Is it safe to flip autoload from yes to no on a large option?

    Usually yes, but test first. Flipping autoload to no means WordPress will not preload that value on every request; the plugin will instead read it on demand when it actually needs it. Some plugins read their settings on every page load anyway, in which case the change saves nothing. Worst case, the plugin reads the option once per request and you have effectively reverted the savings without breaking anything.

    How often should I clean the WordPress database?

    Weekly for WooCommerce and high-edit content sites, monthly for typical blog-style sites, quarterly for low-traffic brochure sites. The recurring tasks worth scheduling: delete expired transients, purge revisions beyond the cap, remove trashed and spam content, and run OPTIMIZE TABLE on the largest tables every quarter.

    Does an object cache (Redis or Memcached) replace database cleanup?

    No. An object cache stores query results in RAM so MySQL is hit less often, but bloated autoload data still has to be loaded into the cache on every cache miss, and the first request after a cache flush still pays the full cost. Cleanup and object caching are complementary: clean the data, then cache what is left.

    Can I just delete the wp_options table and let WordPress rebuild it?

    No. `wp_options` stores active site configuration, including the active theme, active plugins, site URL, permalink structure and admin email. Deleting it will break the site. Only delete individual rows you have positively identified as orphaned, and always with a fresh backup in place.

    Related Guides