Add MySQL Index to WordPress wp_options Table

The wp_options table can be very bloated due to all the settings placed there by themes and plugins. I clean up WordPress options tables quite often for my clients on Codeable to help speed up the wp-admin dashboard area. If you use a plugin that stores a lot of transient or session data in the options table then there are two ways to speed this up: using object cache and adding a MySQL index. The object cache solution will put your options table in a daemon like Redis or Memcached so the values can be fetched from RAM. The other solution is adding an index to the wp_options table so that finding the relevant options takes less time (a MySQL index is similar to an index in a large book).

In this post I will show you how to find out if an index is already added, how to find out if an index is a good idea and finally how to add and remove the wp_options index to see if it improves performance. I highly recommend using New Relic to measure database query time if possible, both Kinsta and Cloudways offer New Relic as part of their hosting services.

Add MySQL Index to WordPress wp_options Table

First find out if any index exists, change wp_options in case you changed your database prefix in wp-config.php or with a plugin.

You can run these commands from phpMyAdmin, Adminer or using WP-CLI‘s wp db query command.

You can also use the SQL Executioner plugin for executing these.

SHOW INDEX FROM wp_options;

Here is the WP-CLI version

wp db query "SHOW INDEX FROM $(wp db prefix --allow-root)options;" --allow-root

If you only see the table below then there is no index present

+------------+------------+-------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
|   Table    | Non_unique |  Key_name   | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+------------+------------+-------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| wp_options |          0 | PRIMARY     |            1 | option_id   | A         |         511 | NULL     | NULL   |      | BTREE      |         |               |
| wp_options |          0 | option_name |            1 | option_name | A         |         511 | NULL     | NULL   |      | BTREE      |         |               |
+------------+------------+-------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+

But if you see an index with a Column_name value of autoload or something similar then you already have an index.

+------------+------------+-------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
|   Table    | Non_unique |  Key_name   | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+------------+------------+-------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| wp_options |          0 | PRIMARY     |            1 | option_id   | A         |         670 | NULL     | NULL   |      | BTREE      |         |               |
| wp_options |          0 | option_name |            1 | option_name | A         |         670 | NULL     | NULL   | YES  | BTREE      |         |               |
| wp_options |          1 | autoload    |            1 | autoload    | A         |           1 | NULL     | NULL   |      | BTREE      |         |               |
+------------+------------+-------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+

A shortcut for listing any wp_options MySQL indices is this command

SHOW INDEX FROM wp_options WHERE column_name != 'option_id' AND column_name != 'option_name';

Here is the WP-CLI version

wp db query "SHOW INDEX FROM $(wp db prefix --allow-root)options WHERE column_name != 'option_id' AND column_name != 'option_name';" --allow-root

First find out if it is a good idea to add an index to your wp_options table by comparing the number of autoload = yes rows to the number of autoload = no rows

SELECT COUNT(CASE WHEN autoload = 'yes' THEN 1 END) FROM wp_options;

WP-CLI version

wp db query "SELECT COUNT(CASE WHEN autoload = 'yes' THEN 1 END) AS AutoloadYes FROM $(wp db prefix --allow-root)options;" --allow-root

Find out how many are not autoloaded with this query

SELECT COUNT(CASE WHEN autoload = 'no' THEN 1 END) FROM wp_options;

WP-CLI version

wp db query "SELECT COUNT(CASE WHEN autoload = 'no' THEN 1 END) AS AutoloadNo FROM $(wp db prefix --allow-root)options;" --allow-root

Generally you only want to make an index if the number of autoload = no options greatly outweigh the autoload = yes options.

As a rule of thumb if 60-80% of the option_name keys are autoload = no values then an index is a good idea.

The MySQL CREATE INDEX syntax takes this format (source)

CREATE INDEX <index-name> ON <table-name>(<column-1>, <column-2>);

You can create the WordPress MySQL index on the wp_options table like this

CREATE INDEX autoloadindex ON wp_options(autoload, option_name);

You can also do it with WP-CLI

wp db query "CREATE INDEX autoloadindex ON $(wp db prefix --allow-root --skip-plugins --skip-themes)options(autoload, option_name);" --allow-root

If you use New Relic (see the benefits with this Kinsta tutorial) you can see the improvement in transaction time, notice how the brown section almost disappears around 8 AM in the graph!

If you want to delete the index, this statement will drop the MySQL index from your wp_options table

DROP INDEX autoloadindex ON wp_options

Here is the WP-CLI version

wp db query "DROP INDEX autoloadindex ON $(wp db prefix --allow-root)options" --allow-root

If you would like to learn more about MySQL indexes in general here is a very thorough post on the topic.

Sources

Stackexchange wp options Table Index
Stackexchange wp_options autoload Index
Cardinality in MySQL Index
Drop Index on MySQL
Optimizing WooCommerce for Thousands of Products
Show Index for MySQL Tables
Difference between ALTER TABLE and CREATE INDEX

12 thoughts on “Add MySQL Index to WordPress wp_options Table”

  1. Thanks for a great article. Could you please suggest a solution for multiste? There is an options table for each site. Also, as new sites come on board, they also won’t have an index, and I’m wondering how we can not only take care of the current sites’ option tables, but automatically include them as they are created.

    • Hey Blue, I would argue that if you need an index for each options table in a multisite then something is not quite right with the setup. If for some reason the options table needs to be that large due to plugins/theme etc then using object cache with Redis, memcached or APCu would be a better solution.

  2. Is an index still necessary when using object cache with redis? Will it boost nay performance? What is the ratio of auto load YES and NO to define if it’s going to be beneficial or not?

    Any plans or i might have missed, but guides for MYSQLD tweaks on the config file on the server? Different everywhere and would be great to see your recommendations of tweaks.

    Thanks!

    • Hey Alriksson, That depends on if the Redis object cache is working well, I haven’t actually seen a bug free object cache solution yet but I am working on one! As for the ratio I’d say if it’s like 60-80% of the option_names are autoload=no then an index is a good idea. You can benchmark the select queries before and after to see the difference, just make sure query cache is disabled or the tests will be erratic.

      The reason you see different recommendations for MySQL is because every database and server is different so there is no one size fits all solution for proper tweaks. They all need to be data-driven and tested thoroughly, this is how I do it for my clients here https://guides.wp-bullet.com/codeable-hire 🙂

      • >That depends on if the Redis object cache is working well, I haven’t actually seen a bug free object cache solution yet but I am working on one!
        Agreed and interesting let me know when you have the bug free solution for us ;).

        >As for the ratio I’d say if it’s like 60-80% of the option_names are autoload=no then an index is a good idea.
        Ok will look into the ratio there, thanks!

        >You can benchmark the select queries before and after to see the difference
        Let me know how to benchmark 🙂

        >Just make sure query cache is disabled or the tests will be erratic.
        Make sense

        >The reason you see different recommendations for MySQL is because every database and server is different so there is no one size fits all solution for proper tweaks. They all need to be data-driven and tested thoroughly.
        Got it, but I assume InnoDB is a good start as storage engine as well. Can I just switch storage engine in MYSQLD or phpmyadmin and it works as it should with new storage engine? I experienced a few issues during the week on the dev environment for a few tables, such as:

        ALTER TABLE `wp_posts`
        ENGINE = INNODB;
        MySQL said: Documentation

        #1067 – Invalid default value for ‘post_date’

    • In these cases you can run a few benchmarks on the queries for autoload yes and autoload no and check the stats to inform your decision 🙂

    • You can probably safely leave the options table alone. You can also create a staging site, create the index and run some test queries to see if there is any meaningful difference 🙂

Comments are closed.