
A freshly installed WordPress site has a lean, fast database. Give it a year of publishing, plugin churn, and routine updates and that same database can balloon with thousands of post revisions, stale transient records, orphaned metadata, and autoloaded rows that inflate every single page load. The good news is that most of this clutter is safe to remove, and the cleanup process is straightforward if you follow a measured, step-by-step approach.
Why Database Bloat Hurts Performance
WordPress loads a significant amount of data on every request. The wp_options table is queried early in every bootstrap cycle, and any row flagged autoload = yes is pulled into memory on every page load regardless of whether the current request actually needs it. When plugins leave orphaned autoloaded rows behind after being deactivated or deleted, that overhead accumulates silently.
Post revisions compound the problem in a different way. Each revision is a full copy of the post stored in wp_posts. A post edited fifty times has fifty extra rows, each with its own corresponding rows in wp_postmeta. Multiply that across hundreds of posts and the table size grows to a point where queries that should be instant start taking measurably longer.
Transients are WordPress’s own short-term caching mechanism. They are meant to expire, but the expiration process depends on a request triggering a cleanup. On busy sites that use a persistent object cache (Redis or Memcached), transients are often stored in memory rather than the database, which is ideal. On sites without one, expired transient rows accumulate in wp_options and are never cleared unless something explicitly cleans them.
Step One: Measure Before You Touch Anything
Never clean what you have not measured. Start with a read-only audit so you know exactly what you are dealing with.
Check table sizes
Run this SQL query via phpMyAdmin, Adminer, or a direct MySQL connection to see which tables are largest:
SELECT
table_name,
ROUND((data_length + index_length) / 1024 / 1024, 2) AS size_mb,
table_rows
FROM information_schema.tables
WHERE table_schema = DATABASE()
ORDER BY (data_length + index_length) DESC;Pay close attention to wp_posts, wp_postmeta, wp_options, and any custom plugin tables.
Count the bloat categories
Use wp-cli to count specific problem areas without modifying anything yet:
# Count post revisions
wp post list --post_type=revision --format=count
# Count expired transients in the database
wp transient list --expired --format=count
# Check autoloaded options size
wp db query "SELECT COUNT(*) AS count, ROUND(SUM(LENGTH(option_value)) / 1024, 2) AS total_kb FROM wp_options WHERE autoload = 'yes';"If your autoloaded options total several megabytes, that is a significant problem worth investigating before running any bulk delete.
Identify which autoloaded options are large
wp db query "SELECT option_name, LENGTH(option_value) AS size_bytes FROM wp_options WHERE autoload = 'yes' ORDER BY size_bytes DESC LIMIT 30;"Look for option names tied to plugins you no longer use, or entries that are clearly serialized caches. These are prime candidates for removal, but research the option name before deleting it. Removing an active plugin’s option without deactivating the plugin first can cause errors.
Step Two: Take a Backup
This is not optional. Before running any destructive query, export the full database:
wp db export backup-before-cleanup.sqlStore that file somewhere off the server. If you have tested, off-site automated backups in place already, confirm the most recent one completed successfully and note its timestamp. A backup from three days ago is not good enough for a database you are about to modify.
Step Three: Clean Revisions
WordPress does not limit revisions by default. Before cleaning old ones, set a cap going forward by adding a line to wp-config.php:
define( 'WP_POST_REVISIONS', 5 );A limit of three to five revisions per post gives you a meaningful undo history without unbounded growth. Once the cap is in place, clean the existing backlog:
# Delete all revisions (runs immediately; backup first)
wp post delete $(wp post list --post_type=revision --format=ids) --forceOn large databases this command can time out. If so, delete in smaller batches by piping through xargs or adding a --posts_per_page limit and running the command multiple times.
Step Four: Delete Expired Transients
wp transient delete --expiredIf you want to clear all transients (not just expired ones) on a site where you have just enabled Redis object caching, use wp transient delete --all. WordPress will regenerate the ones it needs. Only do this if you understand that a cold cache will cause a brief spike in database queries while the cache warms up.
Step Five: Remove Orphaned Post Meta
Orphaned postmeta rows exist when the parent post has been deleted but its metadata was not cleaned up. This SQL query removes them:
wp db query "DELETE pm FROM wp_postmeta pm LEFT JOIN wp_posts p ON p.ID = pm.post_id WHERE p.ID IS NULL;"The same pattern applies to comment meta if you have high comment volume:
wp db query "DELETE cm FROM wp_commentmeta cm LEFT JOIN wp_comments c ON c.comment_ID = cm.comment_id WHERE c.comment_ID IS NULL;"Step Six: Clean Up Orphaned Options from Deleted Plugins
This step requires judgment. Use the autoloaded options audit you ran earlier and compare option names against plugins you know are no longer installed. Common patterns are prefixes like plugin_slug_settings or plugin_slug_cache. Delete them individually rather than with a wildcard to avoid accidents:
wp option delete plugin_slug_settings
wp option delete plugin_slug_transient_cacheIf a row clearly belongs to an active plugin, leave it alone. If you are unsure, leave it alone and do more research first.
Step Seven: Optimize Tables
After bulk deletions, MySQL tables can have gaps in their internal storage. Running OPTIMIZE TABLE reclaims that space and updates table statistics, which helps the query planner make better decisions. wp-cli makes this easy:
wp db optimizeOn InnoDB tables (the default for modern WordPress installs) this operation rebuilds the table, so it can take a few minutes on large datasets. Run it during a low-traffic window.
Keeping Bloat from Coming Back
- Set the revision limit permanently. The
WP_POST_REVISIONSconstant inwp-config.phpis the most effective single change you can make. - Use a persistent object cache. When Redis or Memcached handles transients in memory rather than writing them to the database, the
wp_optionstable stays much cleaner. - Prune plugins aggressively. Every plugin you remove is a potential source of orphaned data you never have to clean. Delete rather than just deactivate plugins you no longer need.
- Schedule periodic audits. A monthly wp-cli check on revision counts and autoload size takes two minutes and catches problems before they become serious.
- Keep WordPress core and plugins updated. Newer versions of popular plugins often improve their own cleanup behavior on deactivation.
A Note on Automated Cleanup Plugins
Several plugins offer one-click database cleanup. They work, and they are a reasonable option for site owners who prefer a GUI. The risk is that automated tools can be overly aggressive and do not know which options belong to plugins you still rely on. If you use one, audit its settings carefully and make sure it is running on a schedule rather than deleting data on activation. The wp-cli approach shown here gives you direct visibility into exactly what is being removed, which is worth the extra effort on any site where data integrity matters.
Takeaway
Database bloat is a gradual, silent problem that gets worse the longer a site has been running without maintenance. The cleanup itself takes less than an hour on most sites: measure, back up, delete revisions, clear transients, remove orphaned rows, optimize tables. The harder part is keeping it clean afterward, and that comes down to three habits: cap revisions in wp-config.php, run a persistent object cache, and uninstall plugins you are done with. Do those three things and you will rarely need to run a major cleanup again.