A real 8-second query scenario on a 50k-row postmeta join, reasoning through indexes, meta_query alternatives, and when to graduate to a custom lookup table.
Works with Claude / GPT1,950 uses★ 4.5
The prompt
code-optimize-slow-wpdb-query
This custom query is taking 8+ seconds on a catalog with about 50,000 products,
filtering by three meta fields at once:
```
SELECT p.ID FROM wp_posts p
INNER JOIN wp_postmeta pm1 ON p.ID = pm1.post_id AND pm1.meta_key = 'brand' AND pm1.meta_value = %s
INNER JOIN wp_postmeta pm2 ON p.ID = pm2.post_id AND pm2.meta_key = 'in_stock' AND pm2.meta_value = '1'
INNER JOIN wp_postmeta pm3 ON p.ID = pm3.post_id AND pm3.meta_key = 'min_price' AND pm3.meta_value < %d
WHERE p.post_status = 'publish' AND p.post_type = 'product'
```
ENVIRONMENT: [MySQL 8.0, wp_postmeta has roughly 900,000 rows total, this query runs on
every category page load, no custom indexes have been added]
Diagnose and fix this like a senior dev doing real query optimization, in order:
1. Explain why this is slow structurally: `wp_postmeta` is an EAV table with
`meta_value` stored as `LONGTEXT`, so MySQL can't efficiently index or range-filter
on it (the `min_price < %d` comparison in particular can't use a normal index
effectively on a text column), and three self-joins on a 900k-row table multiply
the cost.
2. Give the immediate improvement: confirm `meta_key` has an index (it does by
default, but check `SHOW INDEX FROM wp_postmeta`), and rewrite the price comparison
to happen in PHP after a narrower initial query, or store `min_price` as its own
indexed custom column if this filter runs on every page load.
3. Show the equivalent using `WP_Query` with `meta_query`, and explain honestly that
it will likely be similarly slow for this exact case since it generates the same
join pattern under the hood, so this isn't purely a "use WP_Query instead" fix.
4. Recommend the real fix at this scale: a custom lookup table (e.g.
`wp_product_filters` with real typed columns and indexes on `brand`, `in_stock`,
`min_price`) populated via a `save_post` hook, queried directly with `$wpdb->get_col()`
and `$wpdb->prepare()`. Explain the tradeoff: it adds a sync point to keep in sync
with postmeta, but turns an 8-second query into a sub-100ms indexed lookup.
5. Flag the edge case: if this query needs to support arbitrary user-added product
attributes (not just these three fixed fields), a rigid custom table won't scale
either, so consider whether a dedicated search layer (or WooCommerce's own product
attribute lookup table, `wp_wc_product_meta_lookup`, if this is actually a
WooCommerce store) already solves this.
Replace the query, meta fields, and row counts with your actual numbers.
The honest answer in step 3 matters: a lot of “just use WP_Query” advice ignores that meta_query generates the exact same join structure, so it doesn’t fix an EAV performance problem by itself. At real scale, a purpose-built lookup table is usually the only fix that holds.