Skip to content
MySQL tuning my.cnf configuration snippet for WordPress innodb buffer pool
Database Optimization

MySQL Tuning for WordPress: innodb_buffer_pool_size and Key Settings

· · 7 min read

MySQL’s InnoDB storage engine determines how fast WordPress reads and writes data. The default configuration ships tuned for minimal memory use on shared hardware. On a dedicated WordPress server, the defaults leave most of the available RAM unused and produce unnecessary disk I/O. This guide covers the settings that directly affect WordPress query performance, starting with the one that has the highest impact.

innodb_buffer_pool_size: The Most Important Setting

The InnoDB buffer pool is shared memory used to cache data pages, index pages, and other InnoDB structures. When a query can be satisfied from the buffer pool, no disk read occurs. When it cannot, MySQL reads from disk – the single most expensive operation in a WordPress database workload.

The default innodb_buffer_pool_size is 128MB. On a server with 4GB+ of RAM running only MySQL and PHP, this is dramatically undersized.

The standard recommendation is 70-80% of available RAM for a dedicated database server. On a server running both PHP-FPM and MySQL, calculate it as total RAM minus PHP-FPM worker memory (workers x memory_per_worker) minus OS overhead (typically 512MB). On a 4GB server running 20 PHP-FPM workers at 64MB each, that leaves roughly 2.2GB for the buffer pool.


Verifying Buffer Pool Hit Rate

After setting the buffer pool size and running under real traffic for a few hours, check the hit rate. A healthy buffer pool hit rate is above 99%:

If the hit rate is below 99%, the buffer pool is too small to hold the working set of your data. Increase innodb_buffer_pool_size and recheck after another few hours of traffic. Innodb_buffer_pool_reads (physical disk reads) vs Innodb_buffer_pool_read_requests (logical reads) is the metric that matters.


innodb_buffer_pool_instances: Reduce Contention

When multiple threads access the buffer pool simultaneously, they compete for its internal mutex. innodb_buffer_pool_instances splits the pool into independent chunks, each with its own mutex, reducing contention under high-concurrency workloads:

Set this to one instance per GB of buffer pool size, up to 8. So a 4GB buffer pool should use 4 instances. This requires a restart of MySQL to take effect and only applies when innodb_buffer_pool_size is at least 1GB.


max_connections: Match Your PHP-FPM Pool

MySQL’s max_connections limits how many simultaneous client connections it accepts. Each PHP-FPM worker that is executing a WordPress request can hold one MySQL connection. If your PHP-FPM pool has 50 workers and max_connections is 150 (a common default), you have headroom – until a traffic spike pushes PHP-FPM to spawn more workers than your max_connections allows. At that point, new connections fail with “Too many connections”:

Each open connection uses roughly 1MB of MySQL memory. 300 connections = 300MB. Factor this into your total memory budget. For WooCommerce sites where checkout creates long-lived transactions, the actual concurrent connection count is higher than the number of PHP workers would suggest – account for this when sizing max_connections.


thread_cache_size: Reduce Connection Overhead

Creating and destroying OS threads for each connection is expensive. The thread cache keeps recently used threads in memory for reuse. For WordPress with PHP-FPM’s persistent connections disabled (the default), every page load creates and closes a database connection – making the thread cache valuable:

Set it to your expected peak concurrent connections. If PHP-FPM runs 30 workers at peak, set thread_cache_size=30. Monitor the Threads_created status variable – a low rate means the cache is working. A rapidly increasing rate means the cache is too small.


tmp_table_size and max_heap_table_size: Keep Sorts in Memory

WordPress generates queries with GROUP BY and ORDER BY clauses regularly – especially with plugins that do post meta joins. When MySQL sorts these result sets, it creates temporary tables. If the temp table fits in memory, the sort is fast. If it overflows to disk, performance drops significantly:

Both settings must be set together – MySQL uses the lower of the two. Start at 64MB and check Created_tmp_disk_tables vs Created_tmp_tables. If more than 10% of temp tables are going to disk, increase the limit.


Using MySQLTuner to Validate Your Configuration

MySQLTuner is a Perl script that analyzes your MySQL status variables and configuration, then provides specific recommendations. Run it after the server has been under real traffic for at least 24 hours:

MySQLTuner outputs recommendations in three categories: critical failures, performance recommendations, and general suggestions. Pay attention to the buffer pool and temp table recommendations first – these have the highest impact on WordPress performance. Ignore recommendations about the query cache if you are on MySQL 8.x – the query cache was removed in that version.


innodb_log_file_size and Write Performance

The InnoDB redo log records changes before they are written to data files. A larger log file means MySQL can batch more writes before flushing, improving write throughput. For WordPress sites with WooCommerce or high post volume, write performance matters:

Set it to 25% of the buffer pool size. On MySQL 8.0.30+, the parameter was renamed to innodb_redo_log_capacity, which replaces both innodb_log_file_size and innodb_log_files_in_group. The 25% rule still applies as a starting point.


The Complete MySQL Configuration for WordPress

A complete my.cnf configuration block for a 4GB server running WordPress with WooCommerce:

After applying this configuration, restart MySQL and run MySQLTuner again after 24 hours of traffic. The database connection and charset configuration that complements this performance tuning – SSL, collation settings, and repair constants – is covered in the WordPress database connection guide. For the OPcache settings that reduce PHP overhead on top of these database improvements, the PHP OPcache configuration guide walks through memory_consumption, max_accelerated_files, and revalidate_freq settings for WordPress workloads.


Slow Query Log: Finding Unindexed Queries

Before tuning server settings, identify the queries causing the most load. MySQL’s slow query log captures queries that exceed a time threshold and, optionally, queries that do not use indexes:

With log_queries_not_using_indexes=ON, MySQL logs any query that requires a full table scan. On a WordPress site, the most common culprits are meta queries with non-indexed meta keys, ORDER BY clauses on non-indexed columns in wp_posts, and JOINs without proper indexes.

Analyze the slow query log with pt-query-digest (part of Percona Toolkit) to get aggregated counts and average execution times. This tells you which queries to prioritize for index optimization versus which to address through configuration changes. Most WordPress performance problems are better solved by adding an index than by increasing buffer pool size.


Key WordPress Tables and Their Index Patterns

WordPress core creates specific indexes on its tables. Plugins that add custom queries against core tables sometimes miss these indexes or create queries that cannot use them. Knowing the existing indexes helps diagnose slow query log output:

TableKey IndexesCommon Miss
wp_postspost_name, type_status_date, post_parent, post_authorQueries filtering on post_meta without a JOIN to wp_postmeta
wp_postmetapost_id, meta_keyQueries with WHERE meta_value = on high-cardinality meta values
wp_optionsoption_name (unique), autoloadQueries scanning all autoloaded options without WHERE option_name
wp_usermetauser_id, meta_keyQueries joining users to meta with LIKE on meta_key
wp_term_relationshipsterm_taxonomy_idTaxonomy queries with many terms using object_id without the index

For the wp_options table specifically, autoloaded options are the single most common performance issue on mature WordPress sites. Every page load executes SELECT option_name, option_value FROM wp_options WHERE autoload = 'yes'. If this returns 5,000+ options, it is pulling megabytes of data on every request regardless of buffer pool size.


read_rnd_buffer_size and sort_buffer_size

These per-connection buffers are allocated when a query requires a sort or a random read operation. On a server with many concurrent connections, over-allocating them wastes memory. On a server with complex ORDER BY queries, under-allocating forces disk-based sorts:

The defaults (256KB and 2MB respectively) are reasonable for WordPress. Increase sort_buffer_size only if the slow query log shows ORDER BY queries being slow and EXPLAIN confirms they are using filesort. Increasing it beyond 4MB per connection has diminishing returns and adds up quickly across concurrent connections.


innodb_flush_log_at_trx_commit: Durability vs. Performance

This setting controls when MySQL writes and flushes the redo log to disk. The default value of 1 provides full ACID compliance – every committed transaction is guaranteed durable even if the server crashes immediately after. Values 0 and 2 trade some durability for write throughput:

ValueBehaviorRisk
1 (default)Flush to disk on every commitSafe – no data loss on crash
2Write to OS buffer every commit, flush every secondUp to 1 second of transactions lost on OS crash
0Write and flush every second regardless of commitsUp to 1 second of transactions lost on MySQL crash

For WordPress on a server with a RAID controller that has battery-backed write cache, value 2 provides a meaningful write throughput increase with minimal practical risk. For a WooCommerce store, keep it at 1 – losing order data on an OS crash is not an acceptable tradeoff for the performance gain.


query_cache_type: Disable It on MySQL 5.7, Gone on 8.0

MySQL’s query cache caches the full result set of SELECT queries. It sounds useful, but it is a global mutex – every write to any table invalidates all cached queries for that table. On a WordPress site with regular writes (comments, orders, post views), the query cache creates contention that hurts performance more than it helps.

If you are on MySQL 5.7, disable it explicitly. On MySQL 8.0, the query cache was removed entirely.

Object caching (Redis/Memcached) is the correct answer for WordPress query result caching. It caches at the application layer with targeted invalidation, not at the database layer with global table-level invalidation. For the WordPress object cache configuration, the guide on WP memory limits covers how to allocate memory across PHP, the object cache, and the MySQL buffer pool on a single server.


Tune Based on Data, Not Defaults

Run MySQLTuner after 24 hours of real traffic before finalizing any configuration. The status variables it reads reflect your actual workload, not a theoretical benchmark. Buffer pool hit rate, tmp_disk_tables ratio, and thread cache misses all give you specific, actionable numbers. Tune based on those numbers, then recheck.