I have one query that takes several seconds to execute. I'm using version 5.7.19.
SELECT
SQL_CALC_FOUND_ROWS wp_posts.*,
SUM( IFNULL( pvc.count, 0 ) ) AS post_views
FROM
wp_posts
LEFT JOIN wp_term_relationships
ON (wp_posts.ID = wp_term_relationships.object_id)
LEFT JOIN wp_post_views pvc
ON pvc.id = wp_posts.ID
AND pvc.type = 0
AND STR_TO_DATE(pvc.period, '%Y%m%d') BETWEEN NOW() - INTERVAL 30 DAY AND NOW()
WHERE
1=1
AND (wp_term_relationships.term_taxonomy_id IN (2) )
AND wp_posts.post_type = 'post'
AND (wp_posts.post_status = 'publish')
GROUP BY
wp_posts.ID
ORDER BY
post_views DESC, wp_posts.ID DESC
LIMIT
0, 16 ;
The stripped down result:
+-------+------------+
| ID | post_views |
+-------+------------+
| 25324 | 8037 |
| 30866 | 6442 |
| 27720 | 5974 |
| 30161 | 5354 |
| 31207 | 5028 |
| 11901 | 4851 |
| 30739 | 4271 |
| 3331 | 3723 |
| 436 | 3685 |
| 14980 | 3250 |
| 31297 | 3087 |
| 3745 | 3019 |
| 29858 | 2945 |
| 2503 | 2882 |
| 416 | 2880 |
| 23773 | 2577 |
+-------+------------+
Result of the EXPLAIN
query
+----+-------------+-----------------------+------------+--------+---------------------------------------------------------------+----------------------+---------+---------------------------------------------------+------+----------+-----------------------------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-----------------------+------------+--------+---------------------------------------------------------------+----------------------+---------+---------------------------------------------------+------+----------+-----------------------------------------------------------+
| 1 | SIMPLE | wp_term_relationships | NULL | ref | PRIMARY,term_taxonomy_id | term_taxonomy_id | 8 | const | 2190 | 100.00 | Using where; Using index; Using temporary; Using filesort |
| 1 | SIMPLE | wp_posts | NULL | eq_ref | PRIMARY,post_name,type_status_date,post_parent,post_author,ID | PRIMARY | 8 | wpHdStreams.wp_term_relationships.object_id | 1 | 7.80 | Using where |
| 1 | SIMPLE | pvc | NULL | ref | PRIMARY,id_type_period_count,type_period_count,id | id_type_period_count | 9 | wpHdStreams.wp_term_relationships.object_id,const | 16 | 100.00 | Using where; Using index |
+----+-------------+-----------------------+------------+--------+---------------------------------------------------------------+----------------------+---------+---------------------------------------------------+------+----------+-----------------------------------------------------------+
How can I optimize the query? Currently it took at least 2.5 seconds to execute it.
EDIT:
Here are the three tables:
wp_posts
| wp_posts | CREATE TABLE `wp_posts` (
`ID` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`post_author` bigint(20) unsigned NOT NULL DEFAULT '0',
`post_date` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
`post_date_gmt` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
`post_content` longtext COLLATE utf8mb4_unicode_520_ci NOT NULL,
`post_title` text COLLATE utf8mb4_unicode_520_ci NOT NULL,
`post_excerpt` text COLLATE utf8mb4_unicode_520_ci NOT NULL,
`post_status` varchar(20) COLLATE utf8mb4_unicode_520_ci NOT NULL DEFAULT 'publish',
`comment_status` varchar(20) COLLATE utf8mb4_unicode_520_ci NOT NULL DEFAULT 'open',
`ping_status` varchar(20) COLLATE utf8mb4_unicode_520_ci NOT NULL DEFAULT 'open',
`post_password` varchar(255) COLLATE utf8mb4_unicode_520_ci NOT NULL DEFAULT '',
`post_name` varchar(200) COLLATE utf8mb4_unicode_520_ci NOT NULL DEFAULT '',
`to_ping` text COLLATE utf8mb4_unicode_520_ci NOT NULL,
`pinged` text COLLATE utf8mb4_unicode_520_ci NOT NULL,
`post_modified` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
`post_modified_gmt` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
`post_content_filtered` longtext COLLATE utf8mb4_unicode_520_ci NOT NULL,
`post_parent` bigint(20) unsigned NOT NULL DEFAULT '0',
`guid` varchar(255) COLLATE utf8mb4_unicode_520_ci NOT NULL DEFAULT '',
`menu_order` int(11) NOT NULL DEFAULT '0',
`post_type` varchar(20) COLLATE utf8mb4_unicode_520_ci NOT NULL DEFAULT 'post',
`post_mime_type` varchar(100) COLLATE utf8mb4_unicode_520_ci NOT NULL DEFAULT '',
`comment_count` bigint(20) NOT NULL DEFAULT '0',
PRIMARY KEY (`ID`),
KEY `post_name` (`post_name`(191)),
KEY `type_status_date` (`post_type`,`post_status`,`post_date`,`ID`),
KEY `post_parent` (`post_parent`),
KEY `post_author` (`post_author`),
KEY `ID` (`ID`)
) ENGINE=InnoDB AUTO_INCREMENT=33551 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_520_ci |
wp_term_relationships
| wp_term_relationships | 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 `object_id` (`object_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_520_ci |
wp_post_views
| wp_post_views | CREATE TABLE `wp_post_views` (
`id` bigint(20) unsigned NOT NULL,
`type` tinyint(1) unsigned NOT NULL,
`period` varchar(8) COLLATE utf8mb4_unicode_520_ci NOT NULL,
`count` bigint(20) unsigned NOT NULL,
PRIMARY KEY (`type`,`period`,`id`),
UNIQUE KEY `id_type_period_count` (`id`,`type`,`period`,`count`) USING BTREE,
KEY `type_period_count` (`type`,`period`,`count`) USING BTREE,
KEY `id` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_520_ci |
type
indicates the type of the value:
- 0 = Year with month and day (Ymd)
- 1 = Year with (i think it was) week
- 2 = Year with month (Ym)
- 3 = Year (Y)
- 4 = total
It's this plugin post views counter
Best Answer
Without
SHOW CREATE TABLE
for each table, I can only guess...If
wp_term_relationships
is a many:many mapping table, then follow the schema tips seen in http://mysql.rjweb.org/doc.php/index_cookbook_mysql#many_to_many_mapping_tableIf any of those tables is a variant of
wp_postmeta
, then see the tips in http://mysql.rjweb.org/doc.php/index_cookbook_mysql#speeding_up_wp_postmetaAlso,... Check the math. Often, this pattern comes up with inflated aggregates:
I call it the "inflate-deflate syndrome. After seeing the schema, I may have more comments. If we can get rid of it then we can get rid of the
GROUP BY
, which is currently preventing any optimization of theORDER BY
orLIMIT
.The gist of the 'fix' will be to turn
post_views
into a subquery that has all theJOINing
in it. That gets rid of the regularJOIN
and theGROUP BY
. Once you have done all that, do theIFNULL
outside theSUM
.STR_TO_DATE(pvc.period, '%Y%m%d')
is hiding a potentially indexable column inside a function. Ifperiod
is aDATE
datatype, then that is identicalpvc.period
. Even if it is aDATETIME
orTIMESTAMP
, it should be fine in this context. If itVARCHAR
, then shame on you (or on WP?).More (based on schema)
For pvc, this will cut out the redundant indexes: Replace the 4 you have with these 2. (It will not inpact the current query.)
See my link for avoiding the
191
. (Again, won't impact the current query, but might greatly help others.)If you can't change
period
to aDATE
(and it looks like you cannot), then the goal is to keep it as a string and aim for(I don't know if the bogus
CONCATs
are necessary to change those dates into strings.) Notice how the indexedperiod
is standing alone, not hidden inside a function call.Be sure to do likewise for the other "types".
Keep in mind that WP and all its plugins seem to be designed for "small" databases. You have come to me to figure out how to use MySQL to improve performance and scalability, that they have yet to consider.
I think that
LEFT JOIN
should be changed to simplyJOIN
. After all, if the 'right' table is missing, you want0
in theSUM
, so why bother creatingNULLs
to have to turn into0
.Don't use
SQL_CALC_FOUND_ROWS
unless it is important. It involves doing virtually all of the query as if theLIMIT
were absent. (And, again, the return value may be inflated.)This might come closer:
I got rid of
wp_term_relationships
since it is unused. Note: theLEFT
says that it does not matter whether there is a matching row or not.