Taming a Slow WordPress Database

4 min read

Đọc bằng tiếng Việt →

Many slow WordPress sites are slow in the database long before they are slow anywhere else. Plugins accumulate, options tables swell, and a single template can quietly fire hundreds of queries. The good news is that the investigation is methodical. This guide covers how to find the expensive queries, the usual causes, and fixes ordered from least to most risky.

Start by finding the few queries that matter

Install Query Monitor on a staging copy and load the slow page while logged in as an administrator. A write-up from Stack Harbor describes a case where an admin page issued 800 queries yet five of them accounted for most of the time, which matches common experience: the long tail is noise. Use these views:

  • Queries by component shows which plugin or the theme spends the most query time.
  • Queries by caller names the function, which helps when the component is just core.
  • Slowest queries: both sources we read treat anything above roughly 50 ms as worth a look, and the Stack Harbor article calls above 200 ms a clear target.
  • Duplicate queries are each a missed chance to cache.

On a production server where you cannot install a debugging plugin, the MySQL slow query log is the alternative. A low threshold records more detail, but note that a half-second threshold will miss moderately slow offenders, so do not conclude your database is healthy just because the log is quiet.

# my.cnf (illustrative values)
slow_query_log = 1
long_query_time = 0.5
log_queries_not_using_indexes = 1

Three shapes of problem

Many small queries (the N+1 pattern)

Reading post meta inside a loop can issue a query per post. Priming the meta cache for the whole list first turns that into one query. WP_Query normally primes it for you, so this mostly bites custom code that disables the cache or builds ID lists by hand.

$ids = wp_list_pluck( $items, 'ID' );
update_meta_cache( 'post', $ids );

foreach ( $items as $item ) {
    $price = get_post_meta( $item->ID, '_price', true ); // served from cache
}

One slow query

Usually a missing index, often on a table a plugin created. Core tables ship with sensible indexes; plugin tables sometimes do not. WP Engine’s guide illustrates why it matters with a query that takes 20 ms at 10,000 rows but about 2 seconds at a million. It also warns that every index consumes space and slows writes, so test on staging, measure before and after, and keep a rollback plan. For most site owners the practical step is reporting the slow query to the plugin author or host.

The same query repeated

If one identical query runs dozens of times per request, an object cache is the fix. WP Engine calls persistent object caching, with Redis or Memcached, typically the biggest single win, since repeat requests skip the database and transients live in memory instead of the options table.

Autoloaded options

Autoloaded options are fetched on every request. Large leftovers from removed plugins are a classic drag. WordPress 6.6 changed how this works: the core developer note explains that options which do not explicitly request autoloading are no longer autoloaded if their value exceeds 150,000 bytes, and that Site Health shows a critical warning once total autoloaded data passes 800 KB. The note also says new rows may use values such as on, off, auto, auto-on and auto-off, so older SQL that tests only for yes will miss entries.

SELECT option_name, LENGTH(option_value) AS bytes
FROM wp_options
WHERE autoload IN ('yes', 'on', 'auto', 'auto-on')
ORDER BY bytes DESC
LIMIT 20;

Adjust the table prefix for your install. Match each large row to its plugin. Data from deleted plugins is generally safe to clear after a backup; data from active plugins will simply be rewritten, so address the plugin instead.

Transients and revisions

Core removes expired transients on a daily schedule, but leftovers accumulate: those saved without an expiry are never cleaned up, WP-Cron only runs when visitors arrive, and some plugins put cache-like data in the options table. WP Engine’s article makes a sober point about revisions too: deleting 40,000 of them might free 300 MB yet change page load time very little, because those rows are not read on normal page views. It still helps backups and migrations, but do not expect a speed miracle.

Safety rules

  • Back up and test the restore before you delete anything.
  • Make changes on staging first and change one thing at a time.
  • Re-measure after each fix; do not start the next until the last is verified.
  • Use one cleanup plugin, not several.
  • OPTIMIZE TABLE is mostly a space-reclaiming tool on InnoDB rather than a speed one.
  • Advice to enable the MySQL query cache is outdated, since it was removed in MySQL 8.0.

For WooCommerce stores, the Stack Harbor piece also points to Action Scheduler tables growing large and to High-Performance Order Storage as items to review. If your site’s admin or checkout is dragging, our services include a database and query review, and our themes are built to avoid unnecessary queries on the front end.

Further reading

← All posts