Hi,
I hope this is the right place to ask a question about query optimization.
Background:
I have a database which has events, which occur in places (listings). Places
have addresses, and addresses belong to a city. I can select the latest
event within a particular city very efficiently (less than 2ms), but
selecting the latest listing within a city is REALLY slow (10-20 seconds)
despite being almost a subset of the event query.
I have been working on this for about a day, and have tried all sorts of
tweaks to the indexes but to no avail. I always seem to end up with 'using
temporary; using filesort' as the 'extra' content in the explain result. If
anyone has a suggestion for what I might do to fix this, I'd really
appreciate it. If not, I could further de-normalize the database for
performance reasons, but I would feel dirty for doing so.
Here is the fast query (select the latest event within a particular city),
and it's explain.
SELECT
events.*, listings.*, addresses.*
FROM
`events`
LEFT OUTER JOIN
`listings` ON `listings`.id = `events`.listing_id
LEFT OUTER JOIN
`addresses` ON `addresses`.addressable_id = `listings`.id
AND
`addresses`.addressable_type = 'Listing'
WHERE (addresses.parent_city_id = 3)
ORDER BY events.id DESC LIMIT 1
+----+-------------+-----------+--------+----------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------+---------+------------------------------+------+-------------+
| id | select_type | table | type | possible_keys
| key
| key_len | ref | rows | Extra |
+----+-------------+-----------+--------+----------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------+---------+------------------------------+------+-------------+
| 1 | SIMPLE | events | index | index_events_on_listing_id
| PRIMARY
| 4 | NULL | 1 | |
| 1 | SIMPLE | listings | eq_ref | PRIMARY
| PRIMARY
| 4 | ratemyarea.events.listing_id | 1 | Using where |
| 1 | SIMPLE | addresses | ref |
index_addresses_on_parent_city_id_and_addressable_type,index_addresses_on_addressable_type_and_addressable_id,addressable_id_type_city
| index_addresses_on_addressable_type_and_addressable_id | 773 | const,
ratemyarea.listings.id | 1 | Using where |
+----+-------------+-----------+--------+----------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------+---------+------------------------------+------+-------------+
Here is the slow query (select the latest listing within a particular city),
and it's explain
SELECT
listings.*, addresses.*
FROM
`listings`
LEFT OUTER JOIN
`addresses` ON `addresses`.addressable_id = `listings`.id
AND
`addresses`.addressable_type = 'Listing'
WHERE (addresses.parent_city_id = 3)
ORDER BY listings.id DESC LIMIT 1
+----+-------------+-----------+--------+----------------------------------------------------------------------------------------------------------------------------------------+--------------------------+---------+-------------------------------------+-------+----------------------------------------------+
| id | select_type | table | type | possible_keys
| key | key_len | ref
| rows | Extra
|
+----+-------------+-----------+--------+----------------------------------------------------------------------------------------------------------------------------------------+--------------------------+---------+-------------------------------------+-------+----------------------------------------------+
| 1 | SIMPLE | addresses | ref |
index_addresses_on_parent_city_id_and_addressable_type,index_addresses_on_addressable_type_and_addressable_id,addressable_id_type_city
| addressable_id_type_city | 773 | const,const |
25680 | Using where; Using temporary; Using filesort |
| 1 | SIMPLE | listings | eq_ref | PRIMARY
| PRIMARY | 4 |
ratemyarea.addresses.addressable_id | 1 |
|
+----+-------------+-----------+--------+----------------------------------------------------------------------------------------------------------------------------------------+--------------------------+---------+-------------------------------------+-------+----------------------------------------------+
Here is the structure of the tables:
CREATE TABLE `addresses` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`postal_code` varchar(255) DEFAULT NULL,
`addressable_type` varchar(255) DEFAULT NULL,
`addressable_id` int(11) DEFAULT NULL,
`parent_city_id` int(11) DEFAULT NULL,
`lat` decimal(15,10) DEFAULT NULL,
`lng` decimal(15,10) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `index_addresses_on_lat_and_lng` (`lat`,`lng`),
KEY `index_addresses_on_parent_city_id_and_addressable_type`
(`parent_city_id`,`addressable_type`),
KEY `index_addresses_on_addressable_type_and_addressable_id`
(`addressable_type`,`addressable_id`)
) ENGINE=InnoDB AUTO_INCREMENT=120513 DEFAULT CHARSET=utf8;
# Dump of table cities
# ------------------------------------------------------------
CREATE TABLE `cities` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
`country_id` int(11) NOT NULL,
`lat` decimal(15,10) NOT NULL,
`lng` decimal(15,10) NOT NULL,
PRIMARY KEY (`id`),
KEY `index_cities_on_name` (`name`),
KEY `index_cities_on_country_id` (`country_id`),
KEY `index_cities_on_lat_and_lng` (`lat`,`lng`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
# Dump of table events
# ------------------------------------------------------------
CREATE TABLE `events` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`listing_id` int(11) NOT NULL,
`description` text,
`title` varchar(150) NOT NULL,
PRIMARY KEY (`id`),
KEY `index_events_on_title` (`title`),
KEY `index_events_on_listing_id` (`listing_id`)
) ENGINE=InnoDB AUTO_INCREMENT=6359 DEFAULT CHARSET=utf8;
# Dump of table listings
# ------------------------------------------------------------
CREATE TABLE `listings` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) DEFAULT NULL,
`latitude` decimal(15,10) DEFAULT NULL,
`longitude` decimal(15,10) DEFAULT NULL,
`description` text,
PRIMARY KEY (`id`),
KEY `index_listings_on_place` (`name`),
KEY `index_listings_on_latitude_and_longitude` (`latitude`,`longitude`)
) ENGINE=InnoDB AUTO_INCREMENT=109358 DEFAULT CHARSET=utf8;
# Dump of table users
# ------------------------------------------------------------
CREATE TABLE `users` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`email` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `index_users_on_email` (`email`),
KEY `index_users_on_latitude_and_longitude` (`latitude`,`longitude`)
) ENGINE=InnoDB AUTO_INCREMENT=1972 DEFAULT CHARSET=utf8;