Make WordPress Core

Opened 13 years ago

Closed 12 years ago

Last modified 11 years ago

#19729 closed enhancement (fixed)

WP_Meta_Query is inefficient when only specifying keys in "OR" query

Reported by: joehoyle's profile joehoyle Owned by: ryan's profile ryan
Milestone: 3.5 Priority: normal
Severity: normal Version: 3.3
Component: Query Keywords: has-patch 3.5-early commit needs-unit-tests
Focuses: Cc:

Description

Suppose I have a WP_Query like so:

$query = new WP_Query( array( 'meta_query' => array( 
	array( 'key' => 'foo' ),
	array( 'key' => 'bar', 'value' => 'val2' ),
	array( 'key' => 'baz' ),
	array( 'key' => 'froo' ) 'relation' => 'OR' ) ) );

Currently, WP_Meta_Query will JOIN 4 copies of postmeta and use one JOIN per key - however, when using a relation of OR, it's not necessary to use more than one meta JOIN for the key-only queries.

The attached patch will pull out all the "key only" queries and use a single JOIN for them, leaving the remaining quires in-tact as they were before.

This can only be done with an OR query, of course.

Below is the SQL query before the patch:

SELECT SQL_CALC_FOUND_ROWS  wp_posts.* FROM wp_posts  INNER JOIN wp_postmeta ON (wp_posts.ID = wp_postmeta.post_id)
INNER JOIN wp_postmeta AS mt1 ON (wp_posts.ID = mt1.post_id)
INNER JOIN wp_postmeta AS mt2 ON (wp_posts.ID = mt2.post_id)
INNER JOIN wp_postmeta AS mt3 ON (wp_posts.ID = mt3.post_id) WHERE 1=1  AND wp_posts.post_type = 'post' AND (wp_posts.post_status = 'publish' OR wp_posts.post_status = 'private') AND (wp_postmeta.meta_key = 'foo'
OR  (mt1.meta_key = 'bar' AND CAST(mt1.meta_value AS CHAR) = 'val2')
OR mt2.meta_key = 'baz'
OR mt3.meta_key = 'froo' ) GROUP BY wp_posts.ID ORDER BY wp_posts.post_date DESC LIMIT 0, 10

The same after the patch:

SELECT SQL_CALC_FOUND_ROWS  wp_posts.* FROM wp_posts  INNER JOIN wp_postmeta ON wp_posts.ID = wp_postmeta.post_id
INNER JOIN wp_postmeta AS mt1 ON (wp_posts.ID = mt1.post_id) WHERE 1=1  AND wp_posts.post_type = 'post' AND (wp_posts.post_status = 'publish' OR wp_posts.post_status = 'private') AND (wp_postmeta.meta_key = 'foo'
OR wp_postmeta.meta_key = 'baz'
OR wp_postmeta.meta_key = 'froo'
OR  (mt1.meta_key = 'bar' AND CAST(mt1.meta_value AS CHAR) = 'val2') ) GROUP BY wp_posts.ID ORDER BY wp_posts.post_date DESC LIMIT 0, 10

I ran this on a large site (>100k posts), so not sure if the performance gains will be so drastic for smaller sites. Before the patch, with 3 JOINS took around 43seconds. After the patch with 1 Join, 5.2ms. Not sure why the hug difference, presumably multiple JOINS was killing it somehow.

Attachments (4)

meta.php (1.1 KB) - added by joehoyle 13 years ago.
meta.diff (1.1 KB) - added by joehoyle 13 years ago.
19729.diff (1.2 KB) - added by SergeyBiryukov 12 years ago.
query.diff (2.4 KB) - added by joehoyle 12 years ago.
Unit Test for WP_Meta_query

Download all attachments as: .zip

Change History (28)

@joehoyle
13 years ago

@joehoyle
13 years ago

#1 @nacin
13 years ago

  • Milestone changed from Awaiting Review to 3.4

Nice.

#2 @AdamBackstrom
13 years ago

  • Cc adam@… added

#3 @Viper007Bond
13 years ago

Quite the speed improvement!

And here's an easier to read (IMO) of the example:

$query = new WP_Query( array(
	'meta_query' => array(
		array(
			'key' => 'foo'
		),
		array(
			'key' => 'bar',
			'value' => 'val2'
		),
		array(
			'key' => 'baz'
		),
		array(
			'key' => 'froo'
		),
		'relation' => 'OR',
	),
) );

#4 @garyc40
13 years ago

  • Keywords has-patch added

#5 @aaroncampbell
12 years ago

  • Cc aaroncampbell added

#6 @scribu
12 years ago

  • Cc scribu added

#7 @ryan
12 years ago

Perhaps we should do this with #18158 in 3.5.

#8 @nacin
12 years ago

  • Keywords 3.5-early added
  • Milestone changed from 3.4 to Future Release

Agreed. Let's not forget.

#9 @batmoo
12 years ago

  • Cc batmoo@… added

#10 @markjaquith
12 years ago

  • Milestone changed from Future Release to 3.5

Resurrecting this for 3.5.

#11 @joehoyle
12 years ago

Just wanted to bump this, has-patch, presuming needs some peer review?

#12 @ryan
12 years ago

We really need some unit tests here. Then we could clear these patches faster.

#13 follow-up: @joehoyle
12 years ago

I don't mind writing some basic tests - haven't done it for WordPress Core before though, could anyone point me in the right direction to get set up

#14 in reply to: ↑ 13 @SergeyBiryukov
12 years ago

Replying to joehoyle:

I don't mind writing some basic tests - haven't done it for WordPress Core before though, could anyone point me in the right direction to get set up

http://make.wordpress.org/core/handbook/automated-testing/

#15 @sc0ttkclark
12 years ago

  • Cc lol@… added

#16 @nacin
12 years ago

If the relation is not 'OR' then we should be able to bypass that first foreach all together, instead assigning $queries = $this->queries.

#17 @nacin
12 years ago

  • Keywords needs-refresh commit needs-unit-tests added

I really want this, and it is a good commit candidate. But, needs a refresh (see also comment 16) and could use a unit test or two.

#18 @SergeyBiryukov
12 years ago

  • Keywords needs-refresh removed

#19 @ryan
12 years ago

[UT1062]

There's a start on post meta query tests.

#20 @ryan
12 years ago

I've been going through old meta query tickets and creating unit test coverage for each fixed bug.

http://unit-tests.trac.wordpress.org/browser/trunk/tests/post/query.php

Contributions welcome. Soon I think I'll be comfortable landing 19729.diff.

Last edited 12 years ago by ryan (previous) (diff)

#21 @ryan
12 years ago

  • Owner set to ryan
  • Resolution set to fixed
  • Status changed from new to closed

In [22103]:

Improve performance of WP_Meta_Query when doing OR queries on meta keys. Props joehoyle, SergeyBiryukov. fixes #19729

#22 @joehoyle
12 years ago

Added some unit tests for WP_Meta_Query and specially one to address this ticket (and also http://unit-tests.trac.wordpress.org/ticket/122)

First unit test so let me know if anything needs changing

@joehoyle
12 years ago

Unit Test for WP_Meta_query

#23 @scribu
12 years ago

[UT1173]

The only thing I had to change was

@ticket http://core.trac.wordpress.org/ticket/19729

with

@ticket 19729
Note: See TracTickets for help on using tickets.