Frank
Frank

Reputation: 53

Improving MySQL Select statement in WordPress theme

I'm using the WordPress theme Engine from Industrialthemes and see that the rendering of the front page uses a lot of queries that uses around 0.4 seconds to run in my MySQL database. Like this one:

SELECT SQL_NO_CACHE SQL_CALC_FOUND_ROWS wp_posts.ID
FROM wp_posts
INNER JOIN wp_term_relationships ON (wp_posts.ID = wp_term_relationships.object_id)
WHERE 1 =1
  AND (wp_term_relationships.term_taxonomy_id IN (1))
  AND wp_posts.post_type = 'post'
  AND (wp_posts.post_status = 'publish'
       OR wp_posts.post_status = 'closed')
GROUP BY wp_posts.ID
ORDER BY wp_posts.post_date DESC 
LIMIT 0,  5;

Is there any way this query can be improved? As far as I can see, the WordPress installation have the default indexes in place for all involved fields. My knowledge on tuning SQL Select statements is not good so I'm hoping for some experts to help me on this one. Thanks.

(From Comment)

CREATE TABLE wp_term_relationships (
    object_id bigint(20) unsigned NOT NULL DEFAULT '0', 
    term_taxonomy_id bigint(20) unsigned NOT NULL DEFAULT '0', 
    term_order int(11) NOT NULL DEFAULT '0', 
    PRIMARY KEY (object_id,term_taxonomy_id), 
    KEY term_taxonomy_id (term_taxonomy_id), 
    KEY idx1 (object_id)
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci

(Later...)

CREATE TABLE wp_term_relationships (
    object_id bigint(20) unsigned NOT NULL DEFAULT '0', 
    term_taxonomy_id bigint(20) unsigned NOT NULL DEFAULT '0', 
    term_order int(11) NOT NULL DEFAULT '0', 
    PRIMARY KEY (object_id,term_taxonomy_id), 
    KEY term_taxonomy_id (term_taxonomy_id), 
    KEY idx1 (term_taxonomy_id,object_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci

Upvotes: 1

Views: 255

Answers (1)

Rick James
Rick James

Reputation: 142398

wp_term_relationships needs INDEX(term_taxonomy_id, object_id) -- in this order
wp_posts might benefit from INDEX(post_type, ID, post_status, post_date) -- in this order

Both are "covering" indexes.
The former lets the JOIN work efficiently and gives the optimizer the option of starting with wp_term_relationships. And it should replace KEY term_taxonomy_id (term_taxonomy_id).
The latter should work well regardless of which table is picked first.

(More)

SELECT  SQL_NO_CACHE SQL_CALC_FOUND_ROWS p.ID
    FROM  wp_posts AS p
     WHERE p.post_type = 'post'
      AND  p.post_status IN ( 'publish', 'closed' )
      AND EXISTS ( SELECT 1 FROM wp_term_relationships AS tr
                     WHERE p.ID = tr.object_id
                       AND tr.term_taxonomy_id IN (1) )
    ORDER BY  p.post_date DESC
    LIMIT  0, 5;

With this formulation,

If the EXPLAIN starts with p:

p:  (post_date, post_type, post_status, ID)
p:  (post_type, post_status, ID, post_date)
tr:  (object_id, term_taxonomy_id)  -- which you have

If the EXPLAIN starts with tr:

p:  (ID)  -- which you probably have
tr:  (term_taxonomy_id, object_id)

The main problems:

  • The GROUP BY was adding effort. (I eliminated it by changing the JOIN to an EXISTS.)
  • IN ( 'publish', 'closed' ) -- inhibits effective use of index.
  • SQL_CALC_FOUND_ROWS -- means that it can't stop when it gets 5 rows.
  • IN (1) turn into = 1, which is fine; but IN (1,2) is messier.

Or, to be more blunt, WP has not yet been engineered to scale.

Please add the indexes and get the EXPLAIN SELECT.

From pastebin:

SELECT  SQL_NO_CACHE p.ID
    FROM  wp_posts AS p
    WHERE  p.post_type = 'post'
      AND  p.post_status = 'publish'
      AND  EXISTS 
    (
        SELECT  1
            FROM  wp_term_relationships AS tr
            WHERE  p.ID = tr.object_id
              AND  EXISTS 
            (
                SELECT  1
                    from  wp_term_taxonomy AS tt
                    WHERE  tr.term_taxonomy_id = tt.term_taxonomy_id
                      AND  tt.taxonomy = 'post_tag'
                      AND  tt.term_id IN (548, 669) ) 
    );

This is a different query. It needs this also:

tt:  INDEX(term_taxonomy_id, taxonomy,  -- in either order
           term_id)   -- last

And...

SELECT  SQL_NO_CACHE wp_posts.ID
    FROM  wp_posts
    INNER JOIN  wp_term_relationships tr 
           ON (wp_posts.ID = tr.object_id)
    INNER JOIN  wp_term_taxonomy tt
           ON (tr.term_taxonomy_id = tt.term_taxonomy_id)
    WHERE  ( post_type = 'post'
              AND  post_status = 'publish'
              AND  tt.taxonomy = 'post_tag'
              AND  tt.term_id IN (548, 669)  
           )
    GROUP BY  wp_posts.ID;

needs

tt:  INDEX(taxonomy, term_id, term_taxonomy_id)  -- in this order

I would add both of those indexes to tt and see what happens to the EXPLAINs and to performance.

Rewrite query See if this gives you the 'right' answer:

SELECT  p.ID, p.post_name, p.post_title,
        p.post_type, p.post_status,
        tt.term_id as termid, tt.taxonomy
    FROM  wp_posts AS p
    INNER JOIN  wp_term_relationships tr  ON (p.ID = tr.object_id)
    INNER JOIN  wp_term_taxonomy tt  ON (tr.term_taxonomy_id = tt.term_taxonomy_id)
    WHERE  p.post_type = 'post'
      AND  p.post_status = 'publish'
      AND  tt.taxonomy = 'post_tag'
      AND  tt.term_id IN (548, 669)
    ORDER BY  p.ID;

Notes:

  • GROUP BY removed
  • AND/OR probably not working as expected: a AND b OR c is equivalent to (a AND b) OR c, but I think you wanted a AND (b OR c)
  • Did you add the recommended indexes?

Upvotes: 1

Related Questions