How to optimize this query in MySQL - mysql

I have these two tables (Moodle 2.8):
CREATE TABLE `mdl_course` (
`id` bigint(10) NOT NULL AUTO_INCREMENT,
`category` bigint(10) NOT NULL DEFAULT '0',
`sortorder` bigint(10) NOT NULL DEFAULT '0',
`fullname` varchar(254) NOT NULL DEFAULT '',
`shortname` varchar(255) NOT NULL DEFAULT '',
`idnumber` varchar(100) NOT NULL DEFAULT '',
`summary` longtext,
`summaryformat` tinyint(2) NOT NULL DEFAULT '0',
`format` varchar(21) NOT NULL DEFAULT 'topics',
`showgrades` tinyint(2) NOT NULL DEFAULT '1',
`newsitems` mediumint(5) NOT NULL DEFAULT '1',
`startdate` bigint(10) NOT NULL DEFAULT '0',
`marker` bigint(10) NOT NULL DEFAULT '0',
`maxbytes` bigint(10) NOT NULL DEFAULT '0',
`legacyfiles` smallint(4) NOT NULL DEFAULT '0',
`showreports` smallint(4) NOT NULL DEFAULT '0',
`visible` tinyint(1) NOT NULL DEFAULT '1',
`visibleold` tinyint(1) NOT NULL DEFAULT '1',
`groupmode` smallint(4) NOT NULL DEFAULT '0',
`groupmodeforce` smallint(4) NOT NULL DEFAULT '0',
`defaultgroupingid` bigint(10) NOT NULL DEFAULT '0',
`lang` varchar(30) NOT NULL DEFAULT '',
`theme` varchar(50) NOT NULL DEFAULT '',
`timecreated` bigint(10) NOT NULL DEFAULT '0',
`timemodified` bigint(10) NOT NULL DEFAULT '0',
`requested` tinyint(1) NOT NULL DEFAULT '0',
`enablecompletion` tinyint(1) NOT NULL DEFAULT '0',
`completionnotify` tinyint(1) NOT NULL DEFAULT '0',
`cacherev` bigint(10) NOT NULL DEFAULT '0',
`calendartype` varchar(30) NOT NULL DEFAULT '',
PRIMARY KEY (`id`),
KEY `mdl_cour_cat_ix` (`category`),
KEY `mdl_cour_idn_ix` (`idnumber`),
KEY `mdl_cour_sho_ix` (`shortname`),
KEY `mdl_cour_sor_ix` (`sortorder`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `mdl_log` (
`id` bigint(10) NOT NULL AUTO_INCREMENT,
`time` bigint(10) NOT NULL DEFAULT '0',
`userid` bigint(10) NOT NULL DEFAULT '0',
`ip` varchar(45) NOT NULL DEFAULT '',
`course` bigint(10) NOT NULL DEFAULT '0',
`module` varchar(20) NOT NULL DEFAULT '',
`cmid` bigint(10) NOT NULL DEFAULT '0',
`action` varchar(40) NOT NULL DEFAULT '',
`url` varchar(100) NOT NULL DEFAULT '',
`info` varchar(255) NOT NULL DEFAULT '',
PRIMARY KEY (`id`),
KEY `mdl_log_coumodact_ix` (`course`,`module`,`action`),
KEY `mdl_log_tim_ix` (`time`),
KEY `mdl_log_act_ix` (`action`),
KEY `mdl_log_usecou_ix` (`userid`,`course`),
KEY `mdl_log_cmi_ix` (`cmid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
And this query:
SELECT l.id,
l.userid AS participantid,
l.course AS courseid,
l.time,
l.ip,
l.action,
l.info,
l.module,
l.url
FROM mdl_log l
INNER JOIN mdl_course c ON l.course = c.id AND c.category <> 0
WHERE
l.id > [some large id]
AND
l.time > [some unix timestamp]
ORDER BY l.id ASC
LIMIT 0,200
mdl_log table has over 200 milion records, and I need to export it into file using PHP and not die in intent. The main problem here is that executing this is too slow. The main killer here is the join to the mdl_course table. If I remove it, everything works fast.
Here is the explain:
+----+-------------+-------+-------+---------------------------------------------+----------------------+---------+----------------+------+-----------------------------------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+-------+---------------------------------------------+----------------------+---------+----------------+------+-----------------------------------------------------------+
| 1 | SIMPLE | c | range | PRIMARY,mdl_cour_cat_ix | mdl_cour_cat_ix | 8 | NULL | 3152 | Using where; Using index; Using temporary; Using filesort |
| 1 | SIMPLE | l | ref | PRIMARY,mdl_log_coumodact_ix,mdl_log_tim_ix | mdl_log_coumodact_ix | 8 | xray2qasb.c.id | 618 | Using index condition; Using where |
+----+-------------+-------+-------+---------------------------------------------+----------------------+---------+----------------+------+-----------------------------------------------------------+
Is there any way to remove usage of temporary and filesort? What do you propose here?

After some testing this query works fast as expected:
SELECT l.id,
l.userid AS participantid,
l.course AS courseid,
l.time,
l.ip,
l.action,
l.info,
l.module,
l.url
FROM mdl_log l
WHERE
l.id > 123456
AND
l.time > 1234
AND
EXISTS (SELECT * FROM mdl_course c WHERE l.course = c.id AND c.category <> 0 )
ORDER BY l.id ASC
LIMIT 0,200
Thanks to JamieD77 for his suggestion!
execution plan:
+----+--------------------+-------+--------+-------------------------+---------+---------+--------------------+----------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+--------------------+-------+--------+-------------------------+---------+---------+--------------------+----------+-------------+
| 1 | PRIMARY | l | range | PRIMARY,mdl_log_tim_ix | PRIMARY | 8 | NULL | 99962199 | Using where |
| 2 | DEPENDENT SUBQUERY | c | eq_ref | PRIMARY,mdl_cour_cat_ix | PRIMARY | 8 | xray2qasb.l.course | 1 | Using where |
+----+--------------------+-------+--------+-------------------------+---------+---------+--------------------+----------+-------------+

Try moving the category selection outside the JOIN. Here I put it in an IN() which the engine will cache on successive runs. I don't have 200M rows to test on, so YMMV.
DESCRIBE
SELECT l.id,
l.userid AS participantid,
l.course AS courseid,
l.time,
l.ip,
l.action,
l.info,
l.module,
l.url
FROM mdl_log l
WHERE
l.id > 1234567890
AND
l.time > 1234567890
AND
l.course IN (SELECT c.id FROM mdl_course c WHERE c.category > 0)
ORDER BY l.id ASC
LIMIT 0,200;

(In addition to using EXISTS...)
l.id > 123456 AND l.time > 1234
seems to beg for a 2-dimensional index.
99962199 -- the table is very big, correct?
Consider PARTITION BY RANGE on mdl_log on time. But...
Don't have more than about 50 partitions; other inefficiencies kick in then.
Partitioning probably won't help id and time are sorta in lock-step. Typical case: id is AUTO_INCREMENT and time is approximately the time of the INSERT.
If that applies, consider:
PRIMARY KEY(time, id) -- see below
INDEX(id) -- Yes, this is sufficient for `id AUTO_INCREMENT`.
With those indexes, you could efficiently do
WHERE time > ...
ORDER BY time, id
which is probably what you really wanted.

Related

How to optimize the below query and what is (Using where; Using join buffer (Block Nested Loop)) With EXPLAIN

I have been facing an issue with the query. whenever I try to select the query it taking 10 to 15 seconds to execute. and what is (Using where; Using join buffer (Block Nested Loop)) in the explain
The query is
SELECT wp_posts.ID, post_title, post_content, wp_pvc_total.postcount,
wp_pvc_total.postnum
FROM wp_posts
LEFT JOIN wp_term_relationships
ON ( wp_posts.ID =
wp_term_relationships.object_id )
LEFT JOIN wp_term_taxonomy
ON ( wp_term_relationships.term_taxonomy_id =
wp_term_taxonomy.term_taxonomy_id )
LEFT JOIN wp_pvc_total ON ( wp_pvc_total.postnum = wp_posts.ID )
WHERE wp_posts.post_author = 630
AND wp_posts.post_author NOT IN(675)
AND wp_posts.ID != 48075
GROUP BY wp_posts.ID
ORDER BY wp_pvc_total.postcount DESC
LIMIT 0, 9;
Query with explain
+----+-------------+-----------------------+--------+---------------------------------------------------------------------------+-------------+---------+-------------------------------------------------------+-------+--------------------------------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-----------------------+--------+---------------------------------------------------------------------------+-------------+---------+-------------------------------------------------------+-------+--------------------------------------------------------+
| 1 | SIMPLE | wp_posts | range | PRIMARY,post_name,type_status_date,post_parent,post_author,idx_post_title | post_author | 16 | NULL | 1682 | Using index condition; Using temporary; Using filesort |
| 1 | SIMPLE | wp_term_relationships | ref | PRIMARY | PRIMARY | 8 | marriai1_topic.wp_posts.ID | 1 | Using index |
| 1 | SIMPLE | wp_term_taxonomy | eq_ref | PRIMARY | PRIMARY | 8 | marriai1_topic.wp_term_relationships.term_taxonomy_id | 1 | Using index |
| 1 | SIMPLE | wp_pvc_total | ALL | NULL | NULL | NULL | NULL | 19670 | Using where; Using join buffer (Block Nested Loop) |
+----+-------------+-----------------------+--------+---------------------------------------------------------------------------+-------------+---------+-------------------------------------------------------+-------+--------------------------------------------------------+
The MySQL Version is 5.6, Innodb Engine and The table structures are
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 NOT NULL,
`post_title` text NOT NULL,
`post_excerpt` text NOT NULL,
`post_status` varchar(20) NOT NULL DEFAULT 'publish',
`comment_status` varchar(20) NOT NULL DEFAULT 'open',
`ping_status` varchar(20) NOT NULL DEFAULT 'open',
`post_password` varchar(255) NOT NULL DEFAULT '',
`post_name` varchar(200) NOT NULL DEFAULT '',
`to_ping` text NOT NULL,
`pinged` text 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 NOT NULL,
`post_parent` bigint(20) unsigned NOT NULL DEFAULT '0',
`guid` varchar(255) NOT NULL DEFAULT '',
`menu_order` int(11) NOT NULL DEFAULT '0',
`post_type` varchar(20) NOT NULL DEFAULT 'post',
`post_mime_type` varchar(100) 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`),
FULLTEXT KEY `idx_post_title` (`post_title`)
)
and
CREATE TABLE `wp_pvc_total` (
`id` mediumint(9) NOT NULL AUTO_INCREMENT,
`postnum` varchar(255) NOT NULL,
`postcount` int(11) NOT NULL DEFAULT '750',
UNIQUE KEY `id` (`id`)
)
Add INDEX(postnum, postcount)
That will make reaching into wp_pvc_total more efficient. And it is "covering".
BNL is quite efficient, so I am no sure that this index will be better. But it seems like having no useful index is inefficient.
On a separate issue, KEY post_name (post_name(191)) is problematic. See http://mysql.rjweb.org/doc.php/limits#767_limit_in_innodb_indexes

Optimizing a simple query with many left joins

I have quite a large query which is used for a user search on 'map_item'.
SELECT map_item_name
FROM map_item
LEFT JOIN map_section_item ON map_section_item_item_id = map_item_id
LEFT JOIN map_section ON map_section_id = map_section_item_section_id
LEFT JOIN map_item_flag ON map_item_flag_item_id = map_item_id
LEFT JOIN flag ON flag_id = map_item_flag_flag_id
LEFT JOIN map ON map_id = map_section_map_id
LEFT JOIN place_map ON place_map_map_id = map_id
LEFT JOIN place ON place_id = place_map_place_id
LEFT JOIN place_category ON place_category_place_id = place_id
LEFT JOIN category ON category_id = place_category_category_id
LEFT JOIN review ON review_map_item_id = map_item_id
LEFT JOIN map_price ON map_price_item_id = map_item_id
LEFT JOIN county_list ON place_address_county = county_id
'map_item' has 5399 records in total and none of the joined tables have much data in at all.
If I run this query without the left joins (SELECT map_item_name FROM map_item) it returns in 0.00s as expected, but the above query with the joins takes around 10.00s.
All of the left joins are required in the query due to the different filters that the user can apply to the search, however the original query was taking a long time to run (20 seconds or so), and after stripping out most parts of the query I was left with the above (which is just the left joins) and even this is taking 18 seconds to run.
Here is the explain statement from the query:
+----+-------------+-------------------+--------+----------------------------------+----------------------------------+---------+-----------------------------------------------------------+------+-----------------------------------------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------------------+--------+----------------------------------+----------------------------------+---------+-----------------------------------------------------------+------+-----------------------------------------------------------------+
| 1 | SIMPLE | map_item | ALL | NULL | NULL | NULL | NULL | 5455 | NULL |
| 1 | SIMPLE | map_section_item | index | NULL | map_section_item_section_id | 8 | NULL | 5330 | Using where; Using index; Using join buffer (Block Nested Loop) |
| 1 | SIMPLE | map_section | eq_ref | PRIMARY | PRIMARY | 4 | bestmeal.map_section_item.map_section_item_section_id | 1 | NULL |
| 1 | SIMPLE | map_item_flag | ALL | NULL | NULL | NULL | NULL | 1509 | Using where; Using join buffer (Block Nested Loop) |
| 1 | SIMPLE | flag | eq_ref | PRIMARY | PRIMARY | 4 | bestmeal.map_item_flag.map_item_flag_flag_id | 1 | Using index |
| 1 | SIMPLE | map | eq_ref | PRIMARY | PRIMARY | 4 | bestmeal.map_section.map_section_map_id | 1 | Using index |
| 1 | SIMPLE | place_map | index | NULL | branch_map_branch_id | 8 | NULL | 1275 | Using where; Using index; Using join buffer (Block Nested Loop) |
| 1 | SIMPLE | place | eq_ref | PRIMARY | PRIMARY | 4 | bestmeal.place_map.place_map_place_id | 1 | NULL |
| 1 | SIMPLE | place_category | ref | place_category_place_id | place_category_place_id | 4 | bestmeal.place.place_id | 1 | Using index |
| 1 | SIMPLE | category | eq_ref | PRIMARY | PRIMARY | 4 | bestmeal.place_category.place_category_category_id | 1 | Using index |
| 1 | SIMPLE | review | ref | review_map_item_id | review_map_item_id | 4 | bestmeal.map_item.map_item_id | 1 | Using index |
| 1 | SIMPLE | map_price | ref | map_price_item_id | map_price_item_id | 4 | bestmeal.map_item.map_item_id | 1 | Using index |
| 1 | SIMPLE | county_list | eq_ref | PRIMARY | PRIMARY | 4 | bestmeal.place.place_address_county | 1 | Using index |
+----+-------------+-------------------+--------+----------------------------------+----------------------------------+---------+-----------------------------------------------------------+------+-----------------------------------------------------------------+
All of these joins are made against indexed fields, and none of the tables that are joined have any unnecessary indexes in them which could be used instead of the intended index.
I'm not an expert when it comes to optimising queries, but I'm struggling to work out what I can do to speed this query up whilst keeping the left joins. I also can't really think of any alternative solutions which will return the same results without using the joins.
Does anybody have any ideas that will help me to increase the performance on this query or accomplish the user search using a different, faster method?
Edit
Table structures as requested:
CREATE TABLE `map_item` (
`map_item_id` int(11) NOT NULL AUTO_INCREMENT,
`map_item_account_id` int(11) NOT NULL DEFAULT '0',
`map_item_category_id` int(11) NOT NULL,
`map_item_name` varchar(255) DEFAULT NULL,
`map_item_description` text,
`map_item_tags` varchar(255) DEFAULT NULL,
`map_item_type` set('d','f') DEFAULT NULL,
PRIMARY KEY (`map_item_id`),
KEY `map_item_account_id` (`map_item_account_id`),
KEY `map_item_tags` (`map_item_tags`),
KEY `map_item_category_id` (`map_item_category_id`),
FULLTEXT KEY `map_item_keyword_search` (`map_item_name`,`map_item_description`,`map_item_tags`),
FULLTEXT KEY `map_item_name` (`map_item_name`),
FULLTEXT KEY `map_item_description` (`map_item_description`),
FULLTEXT KEY `map_item_tags_2` (`map_item_tags`)
) ENGINE=InnoDB AUTO_INCREMENT=5420 DEFAULT CHARSET=latin1 ROW_FORMAT=COMPACT
CREATE TABLE `map_section_item` (
`map_section_item_id` int(11) NOT NULL AUTO_INCREMENT,
`map_section_item_section_id` int(11) NOT NULL DEFAULT '0',
`map_section_item_item_id` int(11) NOT NULL DEFAULT '0',
PRIMARY KEY (`map_section_item_id`),
KEY `map_section_item_section_id` (`map_section_item_section_id`,`map_section_item_item_id`)
) ENGINE=InnoDB AUTO_INCREMENT=24410 DEFAULT CHARSET=latin1 ROW_FORMAT=COMPACT
CREATE TABLE `map_section` (
`map_section_id` int(11) NOT NULL AUTO_INCREMENT,
`map_section_map_id` int(11) NOT NULL DEFAULT '0',
`map_section_map_draft_id` int(11) NOT NULL DEFAULT '0',
`map_section_column` tinyint(1) NOT NULL DEFAULT '1',
`map_section_name` varchar(255) DEFAULT NULL,
`map_section_description` text,
PRIMARY KEY (`map_section_id`),
KEY `map_section_map_draft_id` (`map_section_map_draft_id`),
KEY `map_section_map_id` (`map_section_map_id`),
FULLTEXT KEY `index_name` (`map_section_name`)
) ENGINE=InnoDB AUTO_INCREMENT=4254 DEFAULT CHARSET=latin1 ROW_FORMAT=COMPACT
CREATE TABLE `map_item_flag` (
`map_item_flag_id` int(11) NOT NULL AUTO_INCREMENT,
`map_item_flag_item_id` int(11) NOT NULL DEFAULT '0',
`map_item_flag_flag_id` int(11) NOT NULL DEFAULT '0',
PRIMARY KEY (`map_item_flag_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1547 DEFAULT CHARSET=latin1 ROW_FORMAT=COMPACT
CREATE TABLE `flag` (
`flag_id` int(11) NOT NULL AUTO_INCREMENT,
`flag_category_id` int(11) NOT NULL DEFAULT '0',
`flag_name` varchar(255) DEFAULT NULL,
`flag_description` varchar(255) DEFAULT NULL,
`flag_img` varchar(255) DEFAULT NULL,
`flag_order` tinyint(2) NOT NULL DEFAULT '0',
PRIMARY KEY (`flag_id`),
KEY `flag_category_id` (`flag_category_id`)
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=latin1 ROW_FORMAT=COMPACT
CREATE TABLE `map` (
`map_id` int(11) NOT NULL AUTO_INCREMENT,
`map_account_id` int(11) NOT NULL DEFAULT '0',
`map_name` varchar(255) DEFAULT NULL,
`map_description` text,
`map_type` set('d','f') DEFAULT NULL,
`map_layout` set('columns','tabs','collapsed') DEFAULT NULL,
PRIMARY KEY (`map_id`),
KEY `map_account_id` (`map_account_id`)
) ENGINE=InnoDB AUTO_INCREMENT=138 DEFAULT CHARSET=latin1 ROW_FORMAT=COMPACT
CREATE TABLE `place_map` (
`place_map_id` int(11) NOT NULL AUTO_INCREMENT,
`place_map_place_id` int(11) NOT NULL DEFAULT '0',
`place_map_map_id` int(11) NOT NULL DEFAULT '0',
`place_map_active` tinyint(1) NOT NULL DEFAULT '0',
PRIMARY KEY (`place_map_id`),
KEY `branch_map_branch_id` (`place_map_place_id`,`place_map_map_id`)
) ENGINE=InnoDB AUTO_INCREMENT=2176 DEFAULT CHARSET=latin1 ROW_FORMAT=COMPACT
CREATE TABLE `place` (
`place_id` int(11) NOT NULL AUTO_INCREMENT,
`place_account_id` int(11) NOT NULL DEFAULT '0',
`place_name` varchar(120) DEFAULT NULL,
`place_alias` varchar(255) DEFAULT NULL,
`place_description` text,
`place_address_line_one` varchar(100) DEFAULT NULL,
`place_address_line_two` varchar(100) DEFAULT NULL,
`place_address_line_three` varchar(100) DEFAULT NULL,
`place_address_town` varchar(100) DEFAULT NULL,
`place_address_county` int(11) NOT NULL DEFAULT '0',
`place_address_postcode` varchar(10) DEFAULT NULL,
`place_address_latitude` decimal(11,8) DEFAULT NULL,
`place_address_longitude` decimal(11,8) DEFAULT NULL,
`place_phone` varchar(20) DEFAULT NULL,
`place_email` varchar(255) DEFAULT NULL,
`place_website` varchar(120) DEFAULT NULL,
`place_flag_initial_email` tinyint(1) NOT NULL DEFAULT '0',
`place_audit_admin_id` int(11) NOT NULL DEFAULT '0',
`place_last_audit_datetime` datetime DEFAULT NULL,
`place_created_by_admin_id` int(11) NOT NULL DEFAULT '0',
`place_created` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`place_tried_google` int(1) NOT NULL DEFAULT '0',
PRIMARY KEY (`place_id`),
KEY `place_account_id` (`place_account_id`),
KEY `place_address_county` (`place_address_county`),
KEY `place_alias` (`place_alias`),
KEY `place_audit_admin_id` (`place_audit_admin_id`),
KEY `place_created_by_admin_id` (`place_created_by_admin_id`),
FULLTEXT KEY `place_name` (`place_name`),
FULLTEXT KEY `place_keyword_search` (`place_name`,`place_address_town`),
FULLTEXT KEY `place_address_town` (`place_address_town`)
) ENGINE=InnoDB AUTO_INCREMENT=135167 DEFAULT CHARSET=latin1 ROW_FORMAT=COMPACT
CREATE TABLE `place_category` (
`place_category_id` int(11) NOT NULL AUTO_INCREMENT,
`place_category_place_id` int(11) NOT NULL DEFAULT '0',
`place_category_category_id` int(11) NOT NULL DEFAULT '0',
PRIMARY KEY (`place_category_id`),
UNIQUE KEY `place_category_place_id` (`place_category_place_id`,`place_category_category_id`)
) ENGINE=InnoDB AUTO_INCREMENT=208987 DEFAULT CHARSET=latin1 ROW_FORMAT=COMPACT
CREATE TABLE `category` (
`category_id` int(11) NOT NULL AUTO_INCREMENT,
`category_name` varchar(255) DEFAULT NULL,
PRIMARY KEY (`category_id`)
) ENGINE=InnoDB AUTO_INCREMENT=168 DEFAULT CHARSET=latin1 ROW_FORMAT=COMPACT
CREATE TABLE `review` (
`review_id` int(11) NOT NULL AUTO_INCREMENT,
`review_user_id` int(11) NOT NULL DEFAULT '0',
`review_place_id` int(11) NOT NULL DEFAULT '0',
`review_map_item_id` int(11) NOT NULL DEFAULT '0',
`review_otm_item_name` varchar(156) DEFAULT NULL,
`review_headline` varchar(255) DEFAULT NULL,
`review_message` text,
`review_rating` tinyint(1) NOT NULL DEFAULT '0',
`review_datetime` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`review_edited_datetime` datetime DEFAULT NULL,
`review_hidden` tinyint(1) NOT NULL DEFAULT '0',
`review_deleted` tinyint(1) NOT NULL DEFAULT '0',
`review_status` set('pending','published','hidden','deleted') NOT NULL,
PRIMARY KEY (`review_id`),
KEY `review_map_item_id` (`review_map_item_id`),
KEY `review_place_id` (`review_place_id`),
KEY `review_user_id` (`review_user_id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=latin1 ROW_FORMAT=COMPACT
CREATE TABLE `map_price` (
`map_price_id` int(11) NOT NULL AUTO_INCREMENT,
`map_price_item_id` int(11) NOT NULL DEFAULT '0',
`map_price_label` varchar(50) DEFAULT NULL,
`map_price_value` decimal(10,2) DEFAULT NULL,
PRIMARY KEY (`map_price_id`),
KEY `map_price_item_id` (`map_price_item_id`)
) ENGINE=InnoDB AUTO_INCREMENT=5872 DEFAULT CHARSET=latin1 ROW_FORMAT=COMPACT
CREATE TABLE `county_list` (
`county_id` int(11) NOT NULL AUTO_INCREMENT,
`county_country_id` int(11) NOT NULL DEFAULT '0',
`county_name` varchar(120) DEFAULT NULL,
`county_alias` varchar(120) DEFAULT NULL,
PRIMARY KEY (`county_id`),
KEY `county_alias` (`county_alias`),
KEY `county_country_id` (`county_country_id`)
) ENGINE=InnoDB AUTO_INCREMENT=142 DEFAULT CHARSET=latin1 ROW_FORMAT=COMPACT
Look at these lines:
LEFT JOIN map_section_item ON map_section_item_item_id = map_item_id
| 1 | SIMPLE | map_section_item | index | NULL | map_section_item_section_id | 8 | NULL | 5330 | Using where; Using index; Using join buffer (Block Nested Loop) |
|
Notice "5330". That means it had to search about 5330 items to find the row it needed.
With a simple INDEX(map_section_item_item_id), it would go directly to the one (or few) row it needed. This would make the query run a lot faster.
Repeat for each other JOIN, at least for those with a "Rows" > 1.
Why LEFT? Is each "right" table optionally missing data?
A side issue: Don't prefix everything with the table name; it is too much clutter.
For MySQL, try using the STRAIGHT_JOIN clause...
SELECT STRAIGHT_JOIN map_item_name
FROM map_item
LEFT JOIN ...
STRAIGHT_JOIN tells MySQL to do the query in the order I've listed. This way it forces the map_item as the primary table and all the rest as lookup secondary tables...

Optimize multiple JOINs in MySQL

I have this MySQL which should be correct syntax:
select c.cat_id,c.cat_name as cat_name,
c.cat_desc, c.cat_image, mi.filename,
l.link_id, l.user_id, l.address,l.city,
l.country,l.link_created,l.link_desc,
l.email,l.fax,l.link_hits, l.link_modified,
l.link_name,l.postcode, l.price,l.link_rating,
l.state,l.telephone,l.link_votes,
l.website, l.link_id, l.link_visited, cf.value
from j25_mt_cats as c,
j25_mt_links as l
LEFT OUTER JOIN j25_mt_cfvalues AS cf ON (cf.link_id = l.link_id),
j25_mt_images AS mi,
j25_mt_cl as cl
UNION ALL
select c.cat_id,c.cat_name as cat_name,
c.cat_desc, c.cat_image, mi.filename,
l.link_id, l.user_id, l.address,l.city,
l.country,l.link_created,l.link_desc,
l.email,l.fax,l.link_hits, l.link_modified,
l.link_name,l.postcode, l.price,l.link_rating,
l.state,l.telephone,l.link_votes,
l.website, l.link_id, l.link_visited, cf.value
FROM j25_mt_cats as c,
j25_mt_links as l
RIGHT OUTER JOIN j25_mt_cfvalues AS cf ON cf.link_id = l.link_id,
j25_mt_images AS mi,
j25_mt_cl as cl
where cf.cf_id = 40 and cl.link_id = l.link_id
AND mi.link_id = l.link_id AND mi.ordering < 2
AND c.cat_id = cl.cat_id and c.cat_published = 1
AND c.cat_approved = 1 and l.link_published = 1 and l.link_approved = 1
AND cf.link_id IS NULL;
The query eats up 3GB+ in the tmp directory and ends up timing out. I'm missing something here, how can I increase the efficiency? My goal here was just adding onto an existing query to grab a value from an additional table (j25_mt_cfvalues).
explain:
+----+--------------+------------+-------+---------------+---------+---------+--------------------------+------+------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+--------------+------------+-------+---------------+---------+---------+--------------------------+------+------------------+
| 1 | PRIMARY | mi | ALL | NULL | NULL | NULL | NULL | 165 | |
| 1 | PRIMARY | c | ALL | NULL | NULL | NULL | NULL | 301 | |
| 1 | PRIMARY | l | ALL | NULL | NULL | NULL | NULL | 2139 | |
| 1 | PRIMARY | cf | ref | link_id | link_id | 4 | db_table.l.link_id | 2 | |
| 1 | PRIMARY | cl | index | NULL | PRIMARY | 4 | NULL | 2742 | Using index |
| 2 | UNION | NULL | NULL | NULL | NULL | NULL | NULL | NULL | Impossible WHERE |
| NULL | UNION RESULT | <union1,2> | ALL | NULL | NULL | NULL | NULL | NULL | |
+----+--------------+------------+-------+---------------+---------+---------+--------------------------+------+------------------+
j25_mt_cats schema:
CREATE TABLE j25_mt_cats (
cat_id int(11) NOT NULL auto_increment,
cat_name varchar(255) NOT NULL,
alias varchar(255) NOT NULL,
title varchar(255) NOT NULL,
cat_desc text NOT NULL,
cat_parent int(11) NOT NULL default '0',
cat_links int(11) NOT NULL default '0',
cat_cats int(11) NOT NULL default '0',
cat_featured tinyint(4) NOT NULL default '0',
cat_image varchar(255) NOT NULL,
cat_published tinyint(4) NOT NULL default '0',
cat_created datetime NOT NULL default '0000-00-00 00:00:00',
cat_approved tinyint(4) NOT NULL default '0',
cat_template varchar(255) NOT NULL default '',
cat_usemainindex tinyint(4) NOT NULL default '0',
cat_allow_submission tinyint(4) NOT NULL default '1',
cat_show_listings tinyint(3) unsigned NOT NULL default '1',
metakey text NOT NULL,
metadesc text NOT NULL,
ordering int(11) NOT NULL default '0',
lft int(11) NOT NULL default '0',
rgt int(11) NOT NULL default '0',
PRIMARY KEY (cat_id),
KEY cat_id (cat_id,cat_published,cat_approved),
KEY cat_parent (cat_parent,cat_published,cat_approved,cat_cats,cat_links),
KEY dtree (cat_published,cat_approved),
KEY lft_rgt (lft,rgt),
KEY func_getPathWay (lft,rgt,cat_id,cat_parent),
KEY alias (alias)
) ENGINE=MyISAM AUTO_INCREMENT=3851 DEFAULT CHARSET=utf8 |
j25_mt_links schema:
CREATE TABLE j25_mt_links (
link_id int(11) NOT NULL auto_increment,
link_name varchar(255) NOT NULL,
alias varchar(255) NOT NULL,
link_desc mediumtext NOT NULL,
user_id int(11) NOT NULL default '0',
link_hits int(11) NOT NULL default '0',
link_votes int(11) NOT NULL default '0',
link_rating decimal(7,6) unsigned NOT NULL default '0.000000',
link_featured smallint(6) NOT NULL default '0',
link_published tinyint(4) NOT NULL default '0',
link_approved int(4) NOT NULL default '0',
link_template varchar(255) NOT NULL,
attribs text NOT NULL,
metakey text NOT NULL,
metadesc text NOT NULL,
internal_notes text NOT NULL,
ordering int(11) NOT NULL default '0',
link_created datetime NOT NULL default '0000-00-00 00:00:00',
publish_up datetime NOT NULL default '0000-00-00 00:00:00',
publish_down datetime NOT NULL default '0000-00-00 00:00:00',
link_modified datetime NOT NULL default '0000-00-00 00:00:00',
link_visited int(11) NOT NULL default '0',
address varchar(255) NOT NULL,
city varchar(255) NOT NULL,
state varchar(255) NOT NULL,
country varchar(255) NOT NULL,
postcode varchar(255) NOT NULL,
telephone varchar(255) NOT NULL,
fax varchar(255) NOT NULL,
email varchar(255) NOT NULL,
website varchar(255) NOT NULL,
price double(9,2) NOT NULL default '0.00',
lat float(10,6) NOT NULL COMMENT 'Latitude',
lng float(10,6) NOT NULL COMMENT 'Longitude',
zoom tinyint(3) unsigned NOT NULL COMMENT 'Map''s zoom level',
PRIMARY KEY (link_id),
KEY link_rating (link_rating),
KEY link_votes (link_votes),
KEY link_name (link_name),
KEY publishing (link_published,link_approved,publish_up,publish_down),
KEY count_listfeatured (link_published,link_approved,link_featured,publish_up,publish_down,link_id),
KEY count_viewowner (link_published,link_approved,user_id,publish_up,publish_down),
KEY mylisting (user_id,link_id),
FULLTEXT KEY link_name_desc (link_name,link_desc)
) ENGINE=MyISAM AUTO_INCREMENT=3229 DEFAULT CHARSET=utf8 |
j25_mt_cfvalues schema:
CREATE TABLE j25_mt_cfvalues (
id int(11) NOT NULL auto_increment,
cf_id int(11) NOT NULL,
link_id int(11) NOT NULL,
value mediumtext NOT NULL,
attachment int(10) unsigned NOT NULL default '0',
counter int(11) NOT NULL default '0',
PRIMARY KEY (id),
KEY cf_id (cf_id,link_id),
KEY link_id (link_id),
KEY value (value(8))
) ENGINE=MyISAM AUTO_INCREMENT=20876 DEFAULT CHARSET=utf8 |
The problem is that your first SQL query does not have any WHERE criteria and is causing a global cartesian across EVERY table your are working. Only in the second query does the WHERE clause get applied.
That said, you had a left and right join for the CF table, but you can't have both a cf=40 and cf IS NULL, so I simplified to just a left join on the ID AND 40... so if there IS a record in the CF table it only shows if it's value is 40... any other value would be ignored..
that said, your query can be simplified down to a single query. I also changed to JOIN syntax instead of WHERE so you and others can see the relation to the tables vs guessing.
select
(all your fields)
from
j25_mt_cats as c
JOIN j25_mt_cl as cl
ON c.cat_id = cl.cat_id
JOIN j25_mt_links as l
ON cl.link_id = l.link_id
AND l.link_published = 1
AND l.link_approved = 1
JOIN j25_mt_images AS mi
ON l.link_id = mi.link_id
AND mi.ordering < 2
LEFT OUTER JOIN j25_mt_cfvalues AS cf
ON l.link_id = cf.link_id
AND cf.cf_id = 40
where
c.cat_published = 1
AND c.cat_approved = 1
ORDER BY
RAND() DESC;
TO help optimize the query, your
j25_mt_cats should have an index on (cat_published, cat_approved)
j25_mt_cl on (cat_id)
j25_mt_links on (link_id, link_published, link_approved)
j25_mt_images on (link_id, ordering)
j25_mt_cfvalues on (link_id, cf_id)

MySQL Get rows that only exist in one table performance

The basics of this query have been asked, and answered, many times before, but I'm still having trouble with performance. Here are the details:
I have the table, Products, that has 105724 rows.
I have an update table, _e360products, that has 51813 rows.
I am matching on an alphanumeric 10 character code, that is indexed (unique) on both tables.
I have tried:
SELECT _e360products.Product_Code, products.StockCode
FROM _e360products Left Join Products ON _e360products.Product_Code = Products.StockCode
WHERE products.StockCode IS NULL
and:
SELECT Product_Code
FROM _e360products
WHERE Product_code NOT IN (SELECT StockCode FROM Products)
and, just for a laugh, even:
SELECT Product_Code
FROM _e360products
WHERE (SELECT count(*) FROM Products WHERE StockCode = Product_code) = 0
None of these have returned results within 20 mins!
If I reverse the queries, i.e. getting unique rows from _e360products, I get results very quickly.
Does anyone have any ideas?
~~~~~ Update ~~~~~
Explain results are:
+----+-------------+---------------+--------+---------------+--------------+---------+------------------------------------------+-------+--------------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+---------------+--------+---------------+--------------+---------+------------------------------------------+-------+--------------------------------------+
| 1 | SIMPLE | _e360products | index | NULL | Product_code | 12 | NULL | 50811 | Using index |
| 1 | SIMPLE | Products | eq_ref | stockcode | stockcode | 12 | plumbase_bkup._e360products.Product_code | 1 | Using where; Using index; Not exists |
+----+-------------+---------------+--------+---------------+--------------+---------+------------------------------------------+-------+--------------------------------------+
CREATE TABLE `_e360products` (
`Product_code` varchar(10) CHARACTER SET latin1 NOT NULL DEFAULT '',
`Manufacturers_code` varchar(255) DEFAULT '',
`Description` varchar(255) DEFAULT '',
`Supplier` varchar(255) DEFAULT '',
`Price` varchar(20) DEFAULT '',
`VAT` varchar(20) DEFAULT '',
`Analysis_code` varchar(20) DEFAULT NULL,
PRIMARY KEY (`Product_code`),
KEY `Product_code` (`Product_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `products` (
`productid` int(11) NOT NULL AUTO_INCREMENT,
`QPUM2` varchar(50) NOT NULL DEFAULT '1',
`NWID` varchar(50) NOT NULL DEFAULT '0',
`NHEI` varchar(50) NOT NULL DEFAULT '0',
`NLEN` varchar(50) NOT NULL DEFAULT '0',
`donotdisplayprice` tinyint(2) DEFAULT '0',
`productname` text,
`stockcode` varchar(10) NOT NULL DEFAULT '',
`analysiscode` varchar(50) DEFAULT '',
`usestockcontrol` int(11) DEFAULT '0',
`stockvalue` int(11) DEFAULT '0',
`stock_notification_level` int(11) DEFAULT '0',
`sectionid` int(11) DEFAULT '0',
`productprice` varchar(50) DEFAULT '',
`productprice_incvat` varchar(50) DEFAULT '',
`deleted` int(11) DEFAULT '0',
PRIMARY KEY (`productid`),
UNIQUE KEY `stockcode` (`stockcode`) USING BTREE,
KEY `deleted` (`deleted`),
KEY `allowordering` (`allowordering`),
) ENGINE=MyISAM AUTO_INCREMENT=147440 DEFAULT CHARSET=latin1;
NoteL Products table doesn't include ALL the fields, as there are quite a few...
Please provide a query execution plan (EXPLAIN), it seems your index is not used. Also show as CREATE TABLEs for both tables.
Typo? StockCode[add space here]IS
SELECT _e360products.Product_Code, products.StockCode
FROM _e360products Left Join Products ON _e360products.Product_Code = Products.StockCode
WHERE products.StockCode IS NULL

MySQL query paralyzes site

Once in a while, at random intervals, our website gets completely paralyzed.
Looking at SHOW FULL PROCESSLIST;, I've noticed that when this happens, there is a specific query that is "Copying to tmp table" for a loooong time (sometimes 350 seconds), and almost all the other queries are "Locked".
The part I don't understand is that 90% of the time, this query runs fine. I see it going through in the process list and it finishes pretty quickly most of the time.
This query is being called by an ajax call on our homepage to display product recommendations based your browsing history (a la amazon).
Just sometimes, randomly (but too often), it gets stuck at "copying to tmp table".
Here is a caught instance of the query that was up 109 seconds when I looked:
SELECT DISTINCT product_product.id, product_product.name, product_product.retailprice, product_product.imageurl, product_product.thumbnailurl, product_product.msrp
FROM product_product, product_xref, product_viewhistory
WHERE
(
(product_viewhistory.productId = product_xref.product_id_1 AND product_xref.product_id_2 = product_product.id)
OR
(product_viewhistory.productId = product_xref.product_id_2 AND product_xref.product_id_1 = product_product.id)
)
AND product_product.outofstock='N'
AND product_viewhistory.cookieId = '188af1efad392c2adf82'
AND product_viewhistory.productId IN (24976, 25873, 26067, 26073, 44949, 16209, 70528, 69784, 75171, 75172)
ORDER BY product_xref.hits DESC
LIMIT 10
Of course the "cookieId" and the list of "productId" changes dynamically depending on the request.
I use php with PDO.
Edit: I figured some of the table structures involved might help:
CREATE TABLE IF NOT EXISTS `product_viewhistory` (
`userId` int(10) unsigned NOT NULL default '0',
`cookieId` varchar(30) collate utf8_unicode_ci NOT NULL,
`productId` int(11) NOT NULL,
`viewTime` timestamp NOT NULL default CURRENT_TIMESTAMP,
KEY `userId` (`userId`),
KEY `cookieId` (`cookieId`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
CREATE TABLE IF NOT EXISTS `product_xref` (
`id` int(11) NOT NULL auto_increment,
`product_id_1` int(11) default NULL,
`product_id_2` int(11) default NULL,
`hits` int(11) NOT NULL default '0',
PRIMARY KEY (`id`),
KEY `IDX_PROD1` (`product_id_1`),
KEY `IDX_PROD2` (`product_id_2`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=184531 ;
CREATE TABLE IF NOT EXISTS `product_product` (
`id` int(11) NOT NULL auto_increment,
`supplierid` int(11) NOT NULL default '0',
`suppliersku` varchar(100) NOT NULL default '',
`name` varchar(100) NOT NULL default '',
`cost` decimal(10,2) NOT NULL default '0.00',
`retailprice` decimal(10,2) NOT NULL default '0.00',
`weight` decimal(10,2) NOT NULL default '0.00',
`imageurl` varchar(255) NOT NULL default '',
`thumbnailurl` varchar(255) NOT NULL default '',
`sizechartlink` varchar(255) NOT NULL default '',
`content` text NOT NULL,
`remark` varchar(100) NOT NULL default '',
`colorchartlink` varchar(255) default NULL,
`outofstock` char(1) NOT NULL default '',
`summary` text NOT NULL,
`freehandoutlink` varchar(255) default NULL,
`msrp` decimal(10,2) default NULL,
`enabled` tinyint(1) NOT NULL default '1',
`sales_score` float NOT NULL default '0',
`sales_score_offset` float NOT NULL default '0',
`date_added` timestamp NULL default CURRENT_TIMESTAMP,
`brand` varchar(255) default NULL,
`tag_status` varchar(20) default NULL,
PRIMARY KEY (`id`),
KEY `product_retailprice_idx` (`retailprice`),
KEY `suppliersku` (`suppliersku`),
FULLTEXT KEY `product_name_summary_ft` (`name`,`summary`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
Also, by request, the result of a EXPLAIN:
+----+-------------+---------------------+------+---------------------+----------+---------+-------+-------+------------------------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+---------------------+------+---------------------+----------+---------+-------+-------+------------------------------------------------+
| 1 | SIMPLE | product_xref | ALL | IDX_PROD1,IDX_PROD2 | NULL | NULL | NULL | 30035 | Using temporary; Using filesort |
| 1 | SIMPLE | product_viewhistory | ref | cookieId | cookieId | 92 | const | 682 | Using where |
| 1 | SIMPLE | product_product | ALL | PRIMARY | NULL | NULL | NULL | 31880 | Range checked for each record (index map: 0x1) |
+----+-------------+---------------------+------+---------------------+----------+---------+-------+-------+------------------------------------------------+
3 rows in set (0.00 sec)
New updated version as I realized I did not need product_viewhistory at all. I was left from older code:
SELECT DISTINCT product_product.id, product_product.name, product_product.retailprice, product_product.imageurl, product_product.thumbnailurl, product_product.msrp
FROM product_product, product_xref
WHERE
(
(product_xref.product_id_1 IN (24976, 25873, 26067, 26073, 44949, 16209, 70528, 69784, 75171, 75172) AND product_xref.product_id_2 = product_product.id)
OR
(product_xref.product_id_2 IN (24976, 25873, 26067, 26073, 44949, 16209, 70528, 69784, 75171, 75172) AND product_xref.product_id_1 = product_product.id)
)
AND product_product.outofstock='N'
ORDER BY product_xref.hits DESC
LIMIT 10
And the new explain:
+----+-------------+-----------------+-------------+---------------------+---------------------+---------+------+-------+-------------------------------------------------------------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-----------------+-------------+---------------------+---------------------+---------+------+-------+-------------------------------------------------------------------------------------+
| 1 | SIMPLE | product_xref | index_merge | IDX_PROD1,IDX_PROD2 | IDX_PROD1,IDX_PROD2 | 5,5 | NULL | 32 | Using sort_union(IDX_PROD1,IDX_PROD2); Using where; Using temporary; Using filesort |
| 1 | SIMPLE | product_product | ALL | PRIMARY | NULL | NULL | NULL | 31880 | Range checked for each record (index map: 0x1) |
+----+-------------+-----------------+-------------+---------------------+---------------------+---------+------+-------+-------------------------------------------------------------------------------------+
2 rows in set (0.00 sec)
The first thing to do is see what MySQL is doing under the hood with EXPLAIN, then go from there. It sounds like you have some indexing to do.
I rewrote your query as:
SELECT DISTINCT
pp.id,
pp.name,
pp.retailprice,
pp.imageurl,
pp.thumbnailurl,
pp.msrp
FROM PRODUCT_PRODUCT pp
LEFT JOIN PRODUCT_XREF px1 ON px1.product_id_2 = pp.id
LEFT JOIN PRODUCT_XREF px2 ON px2.product_id_1 = pp.id
WHERE EXISTS(SELECT NULL
FROM PRODUCT_VIEWHISTORY pvh
WHERE pvh.productid = px1.product_id_1
AND pvh.cookieId = '188af1efad392c2adf82'
AND pvh.productId IN (24976, 25873, 26067, 26073, 44949, 16209, 70528, 69784, 75171, 75172))
OR EXISTS(SELECT NULL
FROM PRODUCT_VIEWHISTORY pvh
WHERE pvh.productid = px2.product_id_2
AND pvh.cookieId = '188af1efad392c2adf82'
AND pvh.productId IN (24976, 25873, 26067, 26073, 44949, 16209, 70528, 69784, 75171, 75172))
AND pp.outofstock = 'N'
ORDER BY GREATEST(px1.hits, px2.hits) DESC
LIMIT 10
It would've been easier if the ORDER BY didn't rely on the PRODUCT_XREF.hits column. Too bad MySQL doesn't support Common Table Expressions (CTEs)/Subquery Factoring...
Having two different product_id references is a highly questionable approach. I recommend reviewing the data model.
You need to optimize your query. Run it from mysql prompt or mysql client with EXPLAIN and check execution plan. You may need to add indexes to your tables. Keep in mind that if you run this query few times
in a row, mysql server will cache results and you shouldn't rely on their fast execution time . Maybe it is the reason why your query runs fine 90% of the time.