Skip to content
Web with Wordpress Logo
  • Home
  • Blogs
  • Pricing
  • About Us
  • Contact Us
Get a quote
Home / Performance
Performance

Slow Meta Queries in WordPress: Why postmeta Gets Slow and What to Do About It

YuviNovember 15, 20267 min read
Slow Meta Queries in WordPress: Why postmeta Gets Slow and What to Do About It

Custom fields are one of WordPress’s most useful features. Event dates, property prices, product specifications, member details: all of it can be stored as post meta and displayed with a few lines of code. Filtering and sorting by those fields is just as easy to write. A WP_Query with a meta_query argument, and you have upcoming events sorted by date or properties filtered by price range. Slow meta queries are the usual reason.

It works beautifully on a site with a few hundred posts. Then the site grows. The postmeta table reaches a few million rows, and those filtered pages start taking two, five, ten seconds to load. The code didn’t change. The data did. Slow meta queries are one of the most common performance problems on growing WordPress sites, and understanding why they happen is the key to fixing them properly.

How postmeta is structured

The wp_postmeta table stores every custom field for every post in a simple structure:

  • meta_id: a unique ID
  • post_id: the post the field belongs to
  • meta_key: the field’s name
  • meta_value: the field’s value, stored as text

This key-value design is flexible. Any plugin can store any field without changing the database schema. But it has trade-offs.

The table has indexes on post_id and on meta_key, the latter only covering the first 191 characters. It has no index on meta_value, because values can be long text of any kind. And because meta_value is a text column, numbers and dates are stored as strings, so comparing them as numbers requires casting every value.

Why meta queries get slow

Consider a query for properties under a certain price, sorted by price. Behind the scenes, WordPress joins wp_posts to wp_postmeta, filters rows where meta_key is “price” and the value cast to a number is below the limit, then sorts by that value.

The meta_key index helps find all “price” rows. But then MySQL must examine every one of those rows, cast its value and compare it, because there’s no index on the value. With 50,000 properties, that’s 50,000 rows to check on every page load. Add a second meta condition, such as bedrooms, and WordPress adds another join to postmeta, multiplying the work.

Sorting by a meta value is often worse, because the sort can’t use an index either, so MySQL creates a temporary table and sorts it.

Common patterns that become slow at scale:

  • Filtering by numeric ranges stored in meta, such as prices, dates or ratings
  • Multiple meta conditions in one query, each adding a join
  • OR relations between meta conditions
  • LIKE comparisons on meta values
  • Ordering by a meta value
  • Queries for posts where a meta key does NOT EXIST, which require a left join and a scan

Finding slow meta queries

Query Monitor

Install Query Monitor on staging or temporarily on production and load the slow page. Its Queries panel shows every query with its time and the code that called it. Slow queries with multiple postmeta joins are easy to spot. The “Queries by Component” view shows which plugin or theme is responsible.

The slow query log

For production traffic, enable the MySQL slow query log with a low threshold and look for queries joining wp_postmeta. They often dominate the report on sites with filtering features.

EXPLAIN

Run the slow query with EXPLAIN in front of it to see how MySQL executes it. Warning signs include “Using temporary; Using filesort”, type ALL on large tables and very high row estimates on the postmeta table.

Fix 1: Use taxonomies for filterable attributes

The single most effective fix is often to store filterable values in taxonomies instead of meta. Taxonomy queries use well-indexed relationship tables and are much faster for “show me all posts with this attribute” questions.

Good candidates for taxonomies:

  • Categories of things, such as property type, cuisine or event type
  • Attributes with a limited set of values, such as number of bedrooms, colour or size
  • Locations, such as city or region
  • Boolean flags used for filtering, such as “featured” or “pet friendly”

WooCommerce follows this pattern: product attributes used in layered navigation are stored as taxonomies, which is why attribute filtering scales better than meta filtering.

Prices and exact dates are less suitable for taxonomies, because they have too many distinct values. For those, other fixes apply.

Fix 2: Store numeric and date values for efficient comparison

When you must filter by range on meta, help MySQL by:

  • Specifying the correct type in meta_query, such as NUMERIC or DATE, so comparisons are correct
  • Storing dates in a sortable format such as YYYY-MM-DD or a Unix timestamp
  • Keeping the number of meta conditions in a single query as low as possible

These don’t make queries index-friendly on their own, but they avoid incorrect results and unnecessary work.

Fix 3: Add a targeted index

The default postmeta indexes don’t cover meta_value. You can add a composite index on meta_key and the beginning of meta_value, which helps equality and some range queries on short values:

The prefix lengths keep the index size manageable. Test on staging first, measure the slow query before and after, and be aware that adding an index to a large table can take a while and lock it, so do it during quiet hours.

Indexes help some queries dramatically and others not at all, particularly those that cast values to numbers. EXPLAIN tells you whether the new index is actually used.

Some performance plugins add a set of improved indexes to core tables for you. They’re worth considering for large sites, after testing.

Fix 4: Cache the results

Many filtered queries return the same results for many visitors. Caching them avoids running the expensive query repeatedly:

  • Use the object cache with a persistent backend such as Redis, so WP_Query results and related metadata are cached between requests.
  • Cache the list of matching post IDs in a transient, invalidated when relevant posts are updated.
  • Use page caching for filter combinations that don’t depend on the user.

Caching doesn’t fix the underlying query, but for popular pages it can turn a slow query that runs thousands of times into one that runs occasionally.

Fix 5: Move data into a custom table

For large datasets with heavy filtering, such as directories, listing sites and booking systems, the cleanest solution is often a custom database table with proper columns and indexes. Instead of five rows in postmeta for price, bedrooms, bathrooms, area and date, you store one row with five typed columns and indexes on the ones you filter by.

Queries against such a table are fast and predictable. The trade-off is development effort: you need code to keep the table in sync with posts, and plugins that expect data in postmeta won’t see it unless you provide it.

Many serious directory and real estate plugins already use custom tables for exactly this reason. WooCommerce’s move of orders to dedicated tables and its product lookup tables follow the same logic.

Fix 6: Use a search engine for complex filtering

For sites with complex, faceted filtering across many fields, a dedicated search engine such as Elasticsearch, OpenSearch or a hosted search service handles queries far better than MySQL. Plugins like ElasticPress route WP_Query calls to the search engine. This is a bigger infrastructure step, but for large catalogues and directories it’s often the right one.

Reduce the work in the first place

Some quick wins apply to almost any site:

  • Don’t use meta_query with NOT EXISTS unless necessary.
  • Avoid ordering by meta when you could order by date or menu order.
  • Limit posts_per_page sensibly, and avoid -1, which loads everything.
  • Set no_found_rows to true when you don’t need pagination, which skips counting all matching rows.
  • Set update_post_meta_cache to false when you don’t need the meta of returned posts.

Choosing the right fix

For most sites, the order is: move filterable categories into taxonomies, add caching for popular queries, test a targeted index, and move to custom tables or a search engine only when the data and traffic justify it. Whatever you choose, measure with Query Monitor and EXPLAIN before and after. Slow meta queries feel mysterious until you look at the rows examined. Then the fix usually becomes obvious.

Frequently asked questions

Does ACF make meta queries slower?

ACF stores fields in postmeta like any other custom field, so the same considerations apply. ACF also stores a reference row for each field, which doubles the rows per field.

Will Redis fix slow meta queries?

It helps by caching results and post meta, but the first execution of each uncached query is still slow. It’s a complement to fixing the query, not a replacement.

How many postmeta rows is too many?

There’s no fixed number. Problems usually appear in the low millions of rows, depending on query patterns and server resources.

Should I clean up postmeta?

Yes, removing orphaned meta from deleted posts and leftover meta from removed plugins reduces table size and helps all queries.

custom fieldsdatabase indexesmeta queriespostmetawp_query

Post navigation

PreviousCleaning Up Autoloaded Options to Speed Up Every WordPress Request
NextLoading Only the Block CSS a Page Needs in WordPress

Keep reading

Performance

Making WordPress Fast on Budget Android Phones and Patchy Mobile Data

November 15, 2026
Performance

Profiling WordPress PHP Performance With SPX, Xdebug and Query Monitor

November 15, 2026
Performance

Loading Only the Block CSS a Page Needs in WordPress

November 15, 2026
Web with Wordpress Logo

We build, fix and grow WordPress websites, from first launch to ongoing care.

Quick links

  • Home
  • Blog
  • About Us
  • Contact Us

Topics

  • WordPress Development
  • SEO
  • WooCommerce
  • WordPress Security
  • Performance
  • Web Strategy

Contact

  • Haryana, India

© 2026 Web With WordPress. All rights reserved.

  • Privacy Policy