Mysql, slow avg query - mysql

I have a single table book_log Mysql 5.7
+------------+----------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------+----------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| book_id | int(11) | YES | MUL | NULL | |
| type | int(11) | NO | MUL | NULL | |
| value | int(11) | YES | | NULL | |
| created_at | datetime | NO | | NULL | |
+------------+----------+------+-----+---------+----------------+
Book table makes connection with series (One series can have many books)
Create table info :
book_log | CREATE TABLE `book_log` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`book_id` int(11) DEFAULT NULL,
`type` int(11) NOT NULL,
`value` int(11) DEFAULT NULL,
`created_at` datetime NOT NULL,
PRIMARY KEY (`id`),
KEY `IDX_7E42115316A2B381` (`book_id`),
KEY `IDX_TYPE` (`type`),
KEY `IDX_ME` (`book_id`,`type`) USING BTREE,
CONSTRAINT `FK_7E42115316A2B381` FOREIGN KEY (`book_id`) REFERENCES `book` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1158962 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci |
book | CREATE TABLE `book` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`series_id` int(11) DEFAULT NULL,
`language_id` int(11) DEFAULT NULL,
`position` int(11) NOT NULL,
`dir` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`title` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`created_at` datetime NOT NULL,
`updated_at` datetime NOT NULL,
PRIMARY KEY (`id`),
KEY `IDX_CBE5A3315278319C` (`series_id`),
KEY `IDX_CBE5A33182F1BAF4` (`language_id`),
CONSTRAINT `FK_CBE5A3315278319C` FOREIGN KEY (`series_id`) REFERENCES `series` (`id`),
CONSTRAINT `FK_CBE5A33182F1BAF4` FOREIGN KEY (`language_id`) REFERENCES `language` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=55022 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci |
I make the avg value for a given series
select AVG(value)
from book_log
join book b on book_log.book_id = b.id
where type = 20 and b.series_id = ?;
Explain :
+----+-------------+----------+------------+------+------------------------------+----------------------+---------+----------------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+----------+------------+------+------------------------------+----------------------+---------+----------------+------+----------+-------------+
| 1 | SIMPLE | b | NULL | ref | PRIMARY,IDX_CBE5A3315278319C | IDX_CBE5A3315278319C | 5 | const | 212 | 100.00 | Using index |
| 1 | SIMPLE | book_log | NULL | ref | IDX_7E42115316A2B381,IDX_ME | IDX_7E42115316A2B381 | 5 | bdd.b.id | 33 | 100.00 | NULL |
+----+-------------+----------+------------+------+------------------------------+----------------------+---------+----------------+------+----------+-------------+
Or
select AVG(value)
from book_log
where type = 20 AND book_id IN (
select id from book where series_id = ?
);
Explain :
+----+-------------+----------+------------+------+--------------------------------------+----------------------+---------+-------------------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+----------+------------+------+--------------------------------------+----------------------+---------+-------------------+------+----------+-------------+
| 1 | SIMPLE | book | NULL | ref | PRIMARY,IDX_CBE5A3315278319C | IDX_CBE5A3315278319C | 5 | const | 212 | 100.00 | Using index |
| 1 | SIMPLE | book_log | NULL | ref | IDX_7E42115316A2B381,IDX_TYPE,IDX_ME | IDX_7E42115316A2B381 | 5 | bdd.book.id | 33 | 3.72 | Using where |
+----+-------------+----------+------------+------+--------------------------------------+----------------------+---------+-------------------+------+----------+-------------+
I have 10 973 results for these query, 42ms for a count(*) but more than 1 sec for the avg query.
I don't understand why is it so long.
Any idea ?
Thx.

You can expect to COUNT(*) be as fast or faster than SUM(somecol) or AVG(othercolumn). Why? The database server is, by the rules of SQL, to apply any optimization that yields the correct anwer. COUNT(*) has some serious optimization to it.
But the aggregate functions that do arithmetic ; they must instead examine every record. So, slower.
You can create a purpose-built index for your query.
It is this:
ALTER TABLE book_log
ADD INDEX type_id_val
(type, book_id, val)
I chose these columns for the index because your query searches for a particuler type in the index. Upon finding the first row of the chosen type, MySQL can range-scan through just the index and not the table. So, faster. It's called a covering index.

Related

How to improve execution time of a Laravel Query Builder generated SQL query

I have three tables that are concerned by this query
CREATE TABLE `tags` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`latName` varchar(191) NOT NULL,
`araName` varchar(191) NOT NULL,
`active` tinyint(1) NOT NULL DEFAULT 0,
`img_name` varchar(191) DEFAULT NULL,
`icon` varchar(191) DEFAULT NULL,
`rgba_color` varchar(191) DEFAULT NULL,
`color` varchar(191) DEFAULT NULL,
`overlay` varchar(191) DEFAULT NULL,
`position` int(11) NOT NULL,
`mdi_icon` varchar(191) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `tags_latname_unique` (`latName`),
UNIQUE KEY `tags_araname_unique` (`araName`)
) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8mb3
CREATE TABLE `newspapers` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`latName` varchar(191) NOT NULL,
`araName` varchar(191) NOT NULL,
`img_name` varchar(191) DEFAULT NULL,
`active` tinyint(1) NOT NULL DEFAULT 0,
PRIMARY KEY (`id`),
UNIQUE KEY `newspapers_latname_unique` (`latName`),
UNIQUE KEY `newspapers_araname_unique` (`araName`)
) ENGINE=InnoDB AUTO_INCREMENT=21 DEFAULT CHARSET=utf8mb3
CREATE TABLE `articles` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`newspaper_id` bigint(20) unsigned NOT NULL,
`tag_id` bigint(20) unsigned NOT NULL,
`seen` int(10) unsigned NOT NULL,
`link` varchar(1000) NOT NULL,
`title` varchar(191) NOT NULL,
`img_name` varchar(191) NOT NULL,
`date` datetime NOT NULL,
`paragraph` text NOT NULL,
`read_time` int(11) DEFAULT NULL,
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `articles_link_unique` (`link`),
UNIQUE KEY `articles_img_name_unique` (`img_name`),
KEY `articles_newspaper_id_foreign` (`newspaper_id`),
KEY `articles_tag_id_foreign` (`tag_id`),
CONSTRAINT `articles_newspaper_id_foreign` FOREIGN KEY (`newspaper_id`) REFERENCES `newspapers` (`id`),
CONSTRAINT `articles_tag_id_foreign` FOREIGN KEY (`tag_id`) REFERENCES `tags` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=47421 DEFAULT CHARSET=utf8mb3
Basically, I want to load the latest 5 articles (ordered by date) that have an active newspaper and active tag.
Right now articles table contains about 40k entries.
This is the query generated by Laravel's query builder
SELECT `articles`.*
FROM `articles`
INNER JOIN `tags` ON `tags`.`id` = `articles`.`tag_id`
AND `tags`.`active` = 1
INNER JOIN `newspapers` ON `newspapers`.`id` = `articles`.`newspaper_id`
AND `newspapers`.`active` = 1
ORDER BY `date` DESC
LIMIT 5;
It takes Mysql about 6sec to run the query, when I remove the ORDER BY clause, the query becomes very fast (0.001sec).
Here is the query explanation:
+------+-------------+------------+--------+-------------------------------------------------------+-------------------------------+---------+------------------------+------+----------------------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+------+-------------+------------+--------+-------------------------------------------------------+-------------------------------+---------+------------------------+------+----------------------------------------------+
| 1 | SIMPLE | newspapers | ALL | PRIMARY | NULL | NULL | NULL | 18 | Using where; Using temporary; Using filesort |
| 1 | SIMPLE | articles | ref | articles_newspaper_id_foreign,articles_tag_id_foreign | articles_newspaper_id_foreign | 8 | mouhim.newspapers.id | 1127 | |
| 1 | SIMPLE | tags | eq_ref | PRIMARY | PRIMARY | 8 | mouhim.articles.tag_id | 1 | Using where |
+------+-------------+------------+--------+-------------------------------------------------------+-------------------------------+---------+------------------------+------+----------------------------------------------+
I tried creating an index on the date attribute but it didn't help.
for convenience, this is how I am using Query Builder for this query:
Article::select("articles.*")
->join("tags", function ($join) {
$join->on("tags.id", "articles.tag_id")
->where("tags.active", 1);
})
->join("newspapers", function ($join) {
$join->on("newspapers.id", "articles.newspaper_id")
->where("newspapers.active", 1);
})
->orderBy("date", "desc")
->paginate(5)
At first, I was using Eloquent (whereHas) but Eloquent was generating non optimized query using (where exists), so I had to go the joins way.
What can I do to improve execution time of this query?
Result of SHOW INDEXES FROM articles;
+----------+------------+-------------------------------+--------------+--------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment | Ignored |
+----------+------------+-------------------------------+--------------+--------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+
| articles | 0 | PRIMARY | 1 | id | A | 36072 | NULL | NULL | | BTREE | | | NO |
| articles | 0 | articles_link_unique | 1 | link | A | 36072 | NULL | NULL | | BTREE | | | NO |
| articles | 0 | articles_img_name_unique | 1 | img_name | A | 36072 | NULL | NULL | | BTREE | | | NO |
| articles | 1 | articles_newspaper_id_foreign | 1 | newspaper_id | A | 32 | NULL | NULL | | BTREE | | | NO |
| articles | 1 | articles_tag_id_foreign | 1 | tag_id | A | 12 | NULL | NULL | | BTREE | | | NO |
| articles | 1 | data | 1 | date | A | 36072 | NULL | NULL | | BTREE | | | NO |
+----------+------------+-------------------------------+--------------+--------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+
This query was suggested by Rick James as a solution
SELECT `articles`.*
FROM `articles`
WHERE EXISTS ( SELECT 1 FROM tags WHERE id = `articles`.`tag_id` and active = 1)
AND EXISTS ( SELECT 1 FROM newspapers WHERE id = `articles`.`newspaper_id` and active = 1)
ORDER BY `date` DESC
LIMIT 5;
Running EXPLAIN on this query yields the following result
+------+-------------+------------+--------+-------------------------------------------------------+-------------------------------+---------+------------------------+------+----------------------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+------+-------------+------------+--------+-------------------------------------------------------+-------------------------------+---------+------------------------+------+----------------------------------------------+
| 1 | PRIMARY | newspapers | ALL | PRIMARY | NULL | NULL | NULL | 18 | Using where; Using temporary; Using filesort |
| 1 | PRIMARY | articles | ref | articles_newspaper_id_foreign,articles_tag_id_foreign | articles_newspaper_id_foreign | 8 | mouhim.newspapers.id | 1127 | |
| 1 | PRIMARY | tags | eq_ref | PRIMARY | PRIMARY | 8 | mouhim.articles.tag_id | 1 | Using where |
+------+-------------+------------+--------+-------------------------------------------------------+-------------------------------+---------+------------------------+------+----------------------------------------------+
Assuming you don't want dups, change to this; it is likely to be much faster:
SELECT `articles`.*
FROM `articles`
WHERE EXISTS ( SELECT 1 FROM tags
WHERE id = `articles`.`tag_id` )
AND EXISTS ( SELECT 1 FROM newspapers
WHERE id = `articles`.`newspaper_id` )
ORDER BY `date` DESC
LIMIT 5;
Also, have this index on articles:
INDEX(date)
(This is a rare use case for starting index with a column that will be used in a 'range'.)
(Sorry, I don't speak 'Laravel'; maybe someone else can help with that part.)
PS. Having 3 UNIQUE keys on a table is highly unusual. It often indicates a problem with the schema design.
each article has one and only one Tag associated with it
Can multiple articles have the same Tag?
when I remove the ORDER BY clause, the query becomes very fast (0.001sec).
That is because you get whatever 5 rows are easy to return to you. Clearly the ORDER BY is part of the requirement. "Using temporary; Using filesort" says there was at least a sort. It will actually be a "file" sort -- because SELECT * includes a TEXT column. (There is a technique to avoid "file", but I don't think it is needed here.)
I am not sure if the two queries are supposed to be same, but they are not.
Anyway for the second query I think this should be better
Article::leftJoin('tags', 'articles.tag_id', '=', 'tags.id)
->where('tags.latName', $tag)
->orderBy("articles.date", "desc")
->select(['articles.*'])
->paginate(5);
The problem is probably, that the subquery you created in whereIn is slowing it down and whereIn itself may as well slow your query. This may be eased by using join and where.
As for the first query, can you show how you did the index for date? :)

Slow query on smaller MySQL server

I am running MySQL 5.7 on both my server and my local machine. I am using Symfony 4.4 and Doctrine.
On my dev machine the following query (with the same DB dumped from the server) executes in ~2s, while it takes 35s+ on the server.
I assume this is linked to limitations of the server (less RAM, etc.) but I can't really throw additional memory in there. Therefore, I am looking at how I could improve the following the query - originally generated by Doctrine.
I replicated the same slowness by executing the same query directly in phpMyAdmin on the server so I know for sure the query is responsible.
I am a bit stuck here and would appreciate any help or pointers in the right direction: Do I need to try to split the queries? Should I try to add indexes (besides the PK and FK the column referenced in the where clause are not indexed) ?
Thank you all for the help!
SELECT DISTINCT id_0 FROM (
SELECT DISTINCT id_0, pivot_price_5 FROM (
SELECT b0_.id AS id_0, b0_.price_drop AS price_drop_1, o1_.id AS id_2, o1_.price AS price_3, o1_.currency AS currency_4, o1_.pivot_price AS pivot_price_5, o1_.price_drop AS price_drop_6, o1_.date AS date_7, p2_.id AS id_8, p2_.name AS name_9, p2_.description AS description_10, p2_.normal_price AS normal_price_11, p2_.link AS link_12, p2_.image_link AS image_link_13, p2_.image_thumb_link AS image_thumb_link_14, p2_.merchant_product_id AS merchant_product_id_15, p2_.slug AS slug_16, p2_.created_at AS created_at_17, p2_.updated_at AS updated_at_18, p3_.id AS id_19, p3_.ean AS ean_20, p3_.last_game_check_date AS last_game_check_date_21, p3_.created_at AS created_at_22, p3_.updated_at AS updated_at_23, g4_.id AS id_24, g4_.game_system_key AS game_system_key_25, g4_.created_at AS created_at_26, g4_.updated_at AS updated_at_27
FROM best_offer b0_
INNER JOIN offer o1_ ON b0_.offer_id = o1_.id
INNER JOIN product_version p2_ ON o1_.product_version_id = p2_.id
INNER JOIN product p3_ ON b0_.product_id = p3_.id
INNER JOIN product_game_system p5_ ON p3_.id = p5_.product_id
INNER JOIN game_system g4_ ON g4_.id = p5_.game_system_id
WHERE (o1_.date >= '2020-07-29 00:00:00' AND o1_.date <= '2020-07-29 23:59:59')
AND o1_.pivot_price >= '0'
AND o1_.pivot_price <= '2208'
AND g4_.game_system_key IN ('NSW', 'PS4', 'ONE')
) dctrn_result_inner
ORDER BY pivot_price_5 ASC
) dctrn_result LIMIT 8 OFFSET 40
For completion sake, the PHP code is:
// In Repository
$qb = $this->createQueryBuilder('best_offer')
->join('best_offer.offer', 'offer')
->addSelect('offer')
->join('offer.productVersion', 'productVersion')
->addSelect('productVersion')
->join('best_offer.product', 'product')
->addSelect('product')
->join('product.gameSystems', 'gameSystems')
->addSelect('gameSystems')
;
$qb
->join('product.game', 'game')
->join('game.ratings', 'game_ratings')
->andWhere('game_ratings.type = :gameRatingType')
->setParameter('gameRatingType', GameRating::TYPE_METACRITIC)
->andWhere('game_ratings.rating > :gameRatingValue')
->setParameter('gameRatingValue', $minMetacritic)
;
$qb = $qb->addCriteria(OfferRepository::createCriteriaOnDate($datetime, 'offer'));
$qb->andWhere('offer.pivotPrice >= :minPivotPrice')
->setParameter('minPivotPrice', $minPivotPrice*100)
;
$qb = $qb->addCriteria(OfferRepository::createCriteriaMaxPivotPrice($maxPivotPrice, 'offer'));
$qb = $qb->addCriteria(GameSystemRepository::createCriteriaSystemsIn($gameSystems, 'gameSystems'));
$qb = $qb->setMaxResults($limit);
foreach ($sortBy as $sortKey => $sortValue) {
$qb = $qb->orderBy($sortKey, $sortValue);
}
return $qb;
called by the PagerFanta in the Controller:
// In Controller
$adapter = new DoctrineORMAdapter($qb);
$pagerFanta = new Pagerfanta($adapter);
$pagerFanta->setMaxPerPage(8);
$pagerFanta->setCurrentPage($page);
Explain results:
+----+-------------+------------+------------+--------+--------------------------------------------------+-----------------------+---------+--------------------------------+------+----------+-----------------------------------------------------------+--+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | |
+----+-------------+------------+------------+--------+--------------------------------------------------+-----------------------+---------+--------------------------------+------+----------+-----------------------------------------------------------+--+
| 1 | PRIMARY | <derived2> | NULL | ALL | NULL | NULL | NULL | NULL | 2268 | 100.00 | Using temporary | |
| 2 | DERIVED | g4_ | NULL | range | PRIMARY,UNIQ_B478BC43A9F4C69F | UNIQ_B478BC43A9F4C69F | 14 | NULL | 3 | 100.00 | Using where; Using index; Using temporary; Using filesort | |
| 2 | DERIVED | p5_ | NULL | ref | PRIMARY,IDX_1857225C4584665A,IDX_1857225C233EEA7 | IDX_1857225C233EEA7 | 4 | vgdeals.g4_.id | 377 | 100.00 | Using index | |
| 2 | DERIVED | p3_ | NULL | eq_ref | PRIMARY | PRIMARY | 4 | vgdeals.p5_.product_id | 1 | 100.00 | Using index | |
| 2 | DERIVED | b0_ | NULL | ref | UNIQ_8B8D09A53C674EE,IDX_8B8D09A4584665A | IDX_8B8D09A4584665A | 4 | vgdeals.p5_.product_id | 40 | 100.00 | NULL | |
| 2 | DERIVED | o1_ | NULL | eq_ref | PRIMARY,IDX_29D6873ED8DB782E | PRIMARY | 4 | vgdeals.b0_.offer_id | 1 | 5.00 | Using where | |
| 2 | DERIVED | p2_ | NULL | eq_ref | PRIMARY | PRIMARY | 4 | vgdeals.o1_.product_version_id | 1 | 100.00 | Using index | |
+----+-------------+------------+------------+--------+--------------------------------------------------+-----------------------+---------+--------------------------------+------+----------+-----------------------------------------------------------+--+
The SHOW CREATE TABLE for the involved tables is below (sorry I couldn't find a way to format this properly in SO):
BEST_OFFER
CREATE TABLE `best_offer` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`product_id` int(11) NOT NULL,
`offer_id` int(11) NOT NULL,
`price_drop` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `UNIQ_8B8D09A53C674EE` (`offer_id`),
KEY `IDX_8B8D09A4584665A` (`product_id`),
CONSTRAINT `FK_8B8D09A4584665A` FOREIGN KEY (`product_id`) REFERENCES `product` (`id`),
CONSTRAINT `FK_8B8D09A53C674EE` FOREIGN KEY (`offer_id`) REFERENCES `offer` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=317260 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
OFFER
CREATE TABLE `offer` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`product_version_id` int(11) NOT NULL,
`price` int(10) unsigned NOT NULL,
`currency` varchar(3) COLLATE utf8mb4_unicode_ci NOT NULL,
`pivot_price` int(11) NOT NULL,
`price_drop` int(11) DEFAULT NULL,
`date` datetime NOT NULL,
PRIMARY KEY (`id`),
KEY `IDX_29D6873ED8DB782E` (`product_version_id`),
CONSTRAINT `FK_29D6873ED8DB782E` FOREIGN KEY (`product_version_id`) REFERENCES `product_version` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=497233 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
PRODUCT_VERSION
CREATE TABLE `product_version` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`merchant_id` int(11) NOT NULL,
`product_id` int(11) NOT NULL,
`name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`description` longtext COLLATE utf8mb4_unicode_ci,
`normal_price` int(10) unsigned DEFAULT NULL,
`link` varchar(4000) COLLATE utf8mb4_unicode_ci NOT NULL,
`image_link` varchar(4000) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`image_thumb_link` varchar(4000) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`merchant_product_id` varchar(30) COLLATE utf8mb4_unicode_ci NOT NULL,
`slug` varchar(300) COLLATE utf8mb4_unicode_ci NOT NULL,
`created_at` datetime NOT NULL,
`updated_at` datetime NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `UNIQ_6EC5C873989D9B62` (`slug`),
KEY `IDX_6EC5C8736796D554` (`merchant_id`),
KEY `IDX_6EC5C8734584665A` (`product_id`),
CONSTRAINT `FK_6EC5C8734584665A` FOREIGN KEY (`product_id`) REFERENCES `product` (`id`),
CONSTRAINT `FK_6EC5C8736796D554` FOREIGN KEY (`merchant_id`) REFERENCES `merchant` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=10775 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
PRODUCT
CREATE TABLE `product` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`ean` char(13) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`created_at` datetime NOT NULL,
`updated_at` datetime NOT NULL,
`game_id` int(11) DEFAULT NULL,
`last_game_check_date` datetime DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `UNIQ_D34A04AD67B1C660` (`ean`),
KEY `IDX_D34A04ADE48FD905` (`game_id`),
CONSTRAINT `FK_D34A04ADE48FD905` FOREIGN KEY (`game_id`) REFERENCES `game` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6450 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
PRODUCT_GAME_SYSTEM
CREATE TABLE `product_game_system` (
`product_id` int(11) NOT NULL,
`game_system_id` int(11) NOT NULL,
PRIMARY KEY (`product_id`,`game_system_id`),
KEY `IDX_1857225C4584665A` (`product_id`),
KEY `IDX_1857225C233EEA7` (`game_system_id`),
CONSTRAINT `FK_1857225C233EEA7` FOREIGN KEY (`game_system_id`) REFERENCES `game_system` (`id`) ON DELETE CASCADE,
CONSTRAINT `FK_1857225C4584665A` FOREIGN KEY (`product_id`) REFERENCES `product` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
GAME_SYSTEM
CREATE TABLE `game_system` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`game_system_key` varchar(3) COLLATE utf8mb4_unicode_ci NOT NULL,
`created_at` datetime NOT NULL,
`updated_at` datetime NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `UNIQ_B478BC43A9F4C69F` (`game_system_key`)
) ENGINE=InnoDB AUTO_INCREMENT=18 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
Finally, here is the SHOW TABLE STATUS
+---------------------+--------+----+---------+--------+------+----------+---+---------+---------+--------+---------------------+---------------------+---------------------+---------------------+--+--------------------+--+--------------------+--+----------+--+--+--+--+
| best_offer | InnoDB | 10 | Dynamic | 307651 | 46 | 14172160 | 0 | 9469952 | 5242880 | 317260 | 2020-07-29 22:16:33 | 2020-07-31 07:59:09 | | NULL | | utf8mb4_unicode_ci | | NULL | | | | | | |
+---------------------+--------+----+---------+--------+------+----------+---+---------+---------+--------+---------------------+---------------------+---------------------+---------------------+--+--------------------+--+--------------------+--+----------+--+--+--+--+
| game_system | InnoDB | 10 | Dynamic | 17 | 963 | 16384 | 0 | 16384 | 0 | 18 | 2020-07-29 22:16:36 | | NULL | | | NULL | | utf8mb4_unicode_ci | | NULL | | | | |
| offer | InnoDB | 10 | Dynamic | 460330 | 60 | 27836416 | 0 | 7880704 | 6291456 | 497233 | 2020-07-29 22:16:44 | 2020-07-31 07:59:09 | | NULL | | utf8mb4_unicode_ci | | NULL | | | | | | |
| product | InnoDB | 10 | Dynamic | 6432 | 63 | 409600 | 0 | 294912 | 0 | 6450 | 2020-07-29 22:16:44 | 2020-07-31 08:00:57 | | NULL | | utf8mb4_unicode_ci | | NULL | | | | | | |
| product_game_system | InnoDB | 10 | Dynamic | 6419 | 33 | 212992 | 0 | 229376 | 0 | | NULL | | 2020-07-29 22:16:44 | 2020-07-31 07:57:15 | | NULL | | utf8mb4_unicode_ci | | NULL | | | | |
| product_version | InnoDB | 10 | Dynamic | 10749 | 2297 | 24690688 | 0 | 1916928 | 7340032 | 10775 | 2020-07-29 22:16:50 | 2020-07-31 07:59:00 | | NULL | | utf8mb4_unicode_ci | | NULL | | | | | | |
+---------------------+--------+----+---------+--------+------+----------+---+---------+---------+--------+---------------------+---------------------+---------------------+---------------------+--+--------------------+--+--------------------+--+----------+--+--+--+--+
The ORDER BY pivot_price_5 ASC is useless. This is because a subquery is, but definition, an unordered set. (Adding a LIMIT makes it no useless.) But it seems like you should get rid of the inner subquery.
DISTINCT with LIMIT -- you are aware that the DISTINCT happens first?
There are two ranges and one IN in the main WHERE; only one of them can use an index. I suggest you have each of thefollowing so that the Optimizer can pick the better. (Note: With a different dataset, the Optimizer may pick a different INDEX, with different performance.)
INDEX(pivot_price)
INDEX(date)
Please provide EXPLAINs, CREATE TABLEs, and SHOW TABLE STATUS. (I want to analyze whether using partitioning for your "2-dimensional" WHERE would be worth pursuing.)
It looks like you are fetching several columns from many of the tables, only to eventually ignore those extra columns. Cleaning that up will help performance.

How to improve query time when explain looks good?

I have big table 'placement' with 12 558 392 records;
When I try to get data using this table I face the performance problem(load time arount 5 seconds).
When I explain this query all looks good but query time too long.
My query example:
SELECT SQL_NO_CACHE om.*
FROM order_materials om
INNER JOIN material m ON om.material_id = m.id AND om.deleted = FALSE
INNER JOIN placement p ON m.id = p.material_id AND m.deleted = FALSE AND p.deleted = FALSE
INNER JOIN block b ON p.block_id = b.id AND b.deleted = FALSE
INNER JOIN orders o ON om.order_id = o.id AND o.deleted = FALSE
INNER JOIN product pr ON pr.mediaPlan_id = p.mediaplan_id
WHERE
b.advTable_id = 139
AND p.date >= '2018-03-01 00:00:00' AND p.date <= '2018-04-01 00:00:00'
GROUP BY om.material_id;
Explain:
+----+-------------+-------+------------+--------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+----------------------+---------+---------------------------+------+----------+----------------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+--------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+----------------------+---------+---------------------------+------+----------+----------------------------------------------+
| 1 | SIMPLE | b | NULL | ref | PRIMARY,FK597C48D47B04A3 | FK597C48D47B04A3 | 5 | const | 455 | 50.00 | Using where; Using temporary; Using filesort |
| 1 | SIMPLE | p | NULL | ref | FK6ADE12E521DC3251,FK6ADE12E59B1CA2F1,FK6ADE12E5AFA9B543,date_ind,placement_b,placement_material_id_mediaplan_id_index,placement_material_id,placement_material_id_mediaplan_id_order_id_block_id_index,block_id_date_ind | FK6ADE12E521DC3251 | 5 | openmarket.b.id | 135 | 0.82 | Using where |
| 1 | SIMPLE | pr | NULL | ref | FKED8DCCEF9B1CA2F1 | FKED8DCCEF9B1CA2F1 | 5 | openmarket.p.mediaplan_id | 1 | 100.00 | Using index |
| 1 | SIMPLE | m | NULL | eq_ref | PRIMARY | PRIMARY | 4 | openmarket.p.material_id | 1 | 50.00 | Using where |
| 1 | SIMPLE | om | NULL | ref | FK_order_materials_1,FK_order_materials_2 | FK_order_materials_2 | 4 | openmarket.p.material_id | 2 | 50.00 | Using where |
| 1 | SIMPLE | o | NULL | eq_ref | PRIMARY | PRIMARY | 4 | openmarket.om.order_id | 1 | 50.00 | Using where |
+----+-------------+-------+------------+--------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+----------------------+---------+---------------------------+------+----------+----------------------------------------------+
SHOW CREATE TABLE order_materials:
CREATE TABLE `order_materials` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`order_id` int(11) NOT NULL,
`material_id` int(11) NOT NULL,
`deleted` bit(1) NOT NULL DEFAULT b'0',
`created_date` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_date` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`last_mod_user_id` int(11) DEFAULT NULL,
`placements_count` int(11) NOT NULL DEFAULT '0',
PRIMARY KEY (`id`),
KEY `FK_order_materials_1` (`order_id`),
KEY `FK_order_materials_2` (`material_id`),
CONSTRAINT `FK_order_materials_1` FOREIGN KEY (`order_id`) REFERENCES `orders` (`id`),
CONSTRAINT `FK_order_materials_2` FOREIGN KEY (`material_id`) REFERENCES `material` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=251369 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
SHOW CREATE TABLE placement;
CREATE TABLE `placement` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`changeDate` datetime DEFAULT NULL,
`date` datetime NOT NULL,
`plannedPosition` tinyint(4) DEFAULT NULL,
`realPosition` tinyint(4) DEFAULT NULL,
`positionWithPolitics` tinyint(4) DEFAULT NULL,
`material_id` int(11) DEFAULT NULL,
`mediaplan_id` int(11) NOT NULL,
`block_id` int(11) DEFAULT NULL,
`visible` bit(1) NOT NULL,
`blockStartTime` datetime DEFAULT NULL,
`price` float DEFAULT NULL,
`pricedPrice` float DEFAULT NULL,
`actualStartTime` datetime DEFAULT NULL,
`playedPosition` tinyint(4) DEFAULT NULL,
`status` int(11) DEFAULT NULL,
`played_material_id` int(11) DEFAULT NULL,
`deleted` bit(1) NOT NULL DEFAULT b'0',
`created_date` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_date` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`last_mod_user_id` int(11) DEFAULT NULL,
`conflict_status_lid` int(11) DEFAULT NULL COMMENT 'lookup category placement_conflict',
`conflict_by_type_in_block` bit(1) NOT NULL DEFAULT b'0',
`conflict_by_type_near` bit(1) NOT NULL DEFAULT b'0',
`conflict_by_time_overflow` bit(1) NOT NULL DEFAULT b'0',
`conflict_by_position` bit(1) NOT NULL DEFAULT b'0',
`order_id` int(11) DEFAULT NULL,
`order_status_lid` int(11) DEFAULT NULL,
`play_type_lid` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `FK6ADE12E521DC3251` (`block_id`),
KEY `FK6ADE12E59B1CA2F1` (`mediaplan_id`),
KEY `FK6ADE12E5AFA9B543` (`material_id`),
KEY `blockstarttime` (`blockStartTime`),
KEY `date_ind` (`date`),
KEY `bst_rp_ind` (`blockStartTime`,`realPosition`),
KEY `status_ind` (`status`),
KEY `FK_played_material` (`played_material_id`),
KEY `FK_placement_1` (`conflict_status_lid`),
KEY `FK_placement_2` (`order_id`),
KEY `FK_placement_3` (`order_status_lid`),
KEY `FK_placement_4` (`play_type_lid`),
KEY `placement_b` (`date`,`blockStartTime`,`block_id`,`plannedPosition`),
KEY `placement_material_id_mediaplan_id_index` (`material_id`,`mediaplan_id`),
KEY `placement_material_id` (`material_id`,`mediaplan_id`,`order_id`),
KEY `placement_material_id_mediaplan_id_order_id_block_id_index` (`material_id`,`mediaplan_id`,`order_id`,`block_id`),
KEY `block_id_date_ind` (`block_id`,`date`),
CONSTRAINT `FK6ADE12E521DC3251` FOREIGN KEY (`block_id`) REFERENCES `block` (`id`),
CONSTRAINT `FK6ADE12E59B1CA2F1` FOREIGN KEY (`mediaplan_id`) REFERENCES `mediaplan` (`id`),
CONSTRAINT `FK6ADE12E5AFA9B543` FOREIGN KEY (`material_id`) REFERENCES `material` (`id`),
CONSTRAINT `FK_placement_1` FOREIGN KEY (`conflict_status_lid`) REFERENCES `lookups` (`id`),
CONSTRAINT `FK_placement_2` FOREIGN KEY (`order_id`) REFERENCES `orders` (`id`),
CONSTRAINT `FK_placement_3` FOREIGN KEY (`order_status_lid`) REFERENCES `lookups` (`id`),
CONSTRAINT `FK_placement_4` FOREIGN KEY (`play_type_lid`) REFERENCES `lookups` (`id`),
CONSTRAINT `FK_played_material` FOREIGN KEY (`played_material_id`) REFERENCES `played_material` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=12578822 DEFAULT CHARSET=utf8 COMMENT='Розміщення рекламного матеріалу')
) ENGINE=InnoDB AUTO_INCREMENT=251369 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
After rewrite query with exist i have next explain
+----+--------------------+-------+------------+--------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------+-----------------------+---------+---------------------------+--------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+--------------------+-------+------------+--------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------+-----------------------+---------+---------------------------+--------+----------+-------------+
| 1 | PRIMARY | om | NULL | ALL | NULL | NULL | NULL | NULL | 243300 | 50.00 | Using where |
| 6 | DEPENDENT SUBQUERY | o | NULL | eq_ref | PRIMARY | PRIMARY | 4 | openmarket.om.order_id | 1 | 50.00 | Using where |
| 2 | DEPENDENT SUBQUERY | m | NULL | eq_ref | PRIMARY | PRIMARY | 4 | openmarket.om.material_id | 1 | 100.00 | Using where |
| 3 | DEPENDENT SUBQUERY | p | NULL | ref | FK6ADE12E5AFA9B543,date_ind,placement_b,placement_material_id_mediaplan_id_index,placement_material_id,placement_material_id_mediaplan_id_order_id_block_id_index | placement_material_id | 5 | openmarket.m.id | 101 | 0.82 | Using where |
| 5 | DEPENDENT SUBQUERY | pr | NULL | ref | FKED8DCCEF9B1CA2F1 | FKED8DCCEF9B1CA2F1 | 5 | openmarket.p.mediaplan_id | 1 | 100.00 | Using index |
| 4 | DEPENDENT SUBQUERY | b | NULL | eq_ref | PRIMARY,FK597C48D47B04A3 | PRIMARY | 4 | openmarket.p.block_id | 1 | 5.00 | Using where |
+----+--------------------+-------+------------+--------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------+-----------------------+---------+---------------------------+--------+----------+-------------+
As far as I understand from your schema, you are looking for a distinct list of "order_materials" to be filtered according to the other listed tables.
In my experience, you can't filter/group/distinct results with joins without suffering the "using temporary - using filesort"
In your case, anyway, since it looks that you don't need the values from the other tables, I believe it would be possible to rewrite your query removing all the joins and just using the EXISTS clause.
I would try something like this:
SELECT SQL_NO_CACHE om.*
FROM order_materials om
where
om.deleted = false
and
exists (
select 1
from material m
where exists
(select 1 from placement p
where m.id = p.material_id
AND m.deleted = FALSE
AND p.deleted = FALSE
and p.date >= '2018-03-01 00:00:00' AND p.date <= '2018-04-01 00:00:00'
and exists (select 1 from block b
where p.block_id = b.id
AND b.deleted = FALSE
and b.advTable_id = 139
)
and exists (select 1 from product pr
where pr.mediaPlan_id = p.mediaplan_id)
)
and m.id=om.material_id
)
and exists (select 1 from orders o
where om.order_id = o.id
AND o.deleted = FALSE)
The explain plan of such a query is:
+------+--------------------+-------------+--------+-----------------------------------+--------------+---------+--------------------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+------+--------------------+-------------+--------+-----------------------------------+--------------+---------+--------------------+------+-------------+
| 1 | PRIMARY | om | ALL | order_id,material_id | NULL | NULL | NULL | 1 | Using where |
| 1 | PRIMARY | m | eq_ref | PRIMARY | PRIMARY | 4 | abc.om.material_id | 1 | Using where |
| 1 | PRIMARY | o | eq_ref | PRIMARY | PRIMARY | 4 | abc.om.order_id | 1 | Using where |
| 3 | DEPENDENT SUBQUERY | p | ALL | material_id,block_id,mediaplan_id | NULL | NULL | NULL | 1 | Using where |
| 3 | DEPENDENT SUBQUERY | <subquery5> | eq_ref | distinct_key | distinct_key | 4 | func | 1 | |
| 3 | DEPENDENT SUBQUERY | b | eq_ref | PRIMARY,advTable_id | PRIMARY | 4 | abc.p.block_id | 1 | Using where |
| 5 | MATERIALIZED | pr | index | mediaPlan_id | mediaPlan_id | 5 | NULL | 1 | Using index |
+------+--------------------+-------------+--------+-----------------------------------+--------------+---------+--------------------+------+-------------+
As you can see, no temporary, no filesort. Therefore, since exists is a boolean operator, you won't receive duplicates as it happens when you join.
Finally, please, be extremely careful: I wrote this answer on the fly, you need to check that the nested exists are consistent with your expected results, this iis not the final solution but just a shared hint on what I've learned so far.
order_materials seems to be a many:many mapping table. The schema is inefficient. Get rid of id and make the other changes suggested in here .
block needs INDEX(advTable_id, deleted, id)
placement needs INDEX(material_id, deleted, date)
There are several redundant indexes. Follow this rule: If you have INDEX(a, b), you don't need INDEX(a).
key_len = 5 usually refers to INT NULL -- check to see if they should be INT NOT NULL.

Use of MySQL explain

I have this query:
EXPLAIN EXTENDED
SELECT DISTINCT
PMS_STAGIONI.DINIZVAL,
PMS_STAGIONI.DFINEVAL,
PMS_DISPO.DDATA
FROM
PMS_DISPO JOIN PMS_STAGIONI
HAVING
PMS_DISPO.DDATA BETWEEN PMS_STAGIONI.DINIZVAL AND PMS_STAGIONI.DFINEVAL
The output of explain is:
+----+-------------+--------------+-------+---------------+------------------------------+---------+------+------+----------+--------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+--------------+-------+---------------+------------------------------+---------+------+------+----------+--------------------------------+
| 1 | SIMPLE | PMS_STAGIONI | index | NULL | IDX_INIZFINEVAL_PMS_STAGIONI | 6 | NULL | 3 | 100.00 | Using index; Using temporary |
| 1 | SIMPLE | PMS_DISPO | index | NULL | IDX_DDATA_PMS_DISPO | 3 | NULL | 1199 | 100.00 | Using index; Using join buffer |
+----+-------------+--------------+-------+---------------+------------------------------+---------+------+------+----------+--------------------------------+
My question is how to calculate the product of the join using explain. For example, in this case are performed 3597 (1199x3) scans or only 1199?
1)If I add "ORDER BY DDATA" lines scanned in the table "PMS_DISPO" become 1130.
2)If I use the "WHERE" clause instead of "HAVING" clause scan no longer uses the indexes. How is it possible?
3)If i want show PMS_STAGIONI.CSTAGIONI (primary key) explain show me that:
+----+-------------+--------------+-------+---------------+---------------------+---------+------+------+----------+--------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+--------------+-------+---------------+---------------------+---------+------+------+----------+--------------------------------+
| 1 | SIMPLE | PMS_STAGIONI | ALL | NULL | NULL | NULL | NULL | 3 | 100.00 | Using temporary |
| 1 | SIMPLE | PMS_DISPO | index | NULL | IDX_DDATA_PMS_DISPO | 3 | NULL | 1130 | 100.00 | Using index; Using join buffer |
+----+-------------+--------------+-------+---------------+---------------------+---------+------+------+----------+--------------------------------+
How can I force the use of the other index?
Thanks in advance.
Edit:
The structure of "PMS_DISPO" is:
CREATE TABLE IF NOT EXISTS `PMS_DISPO` (
`ID` int(11) NOT NULL AUTO_INCREMENT ,
`CPRENOTA` int(11) NOT NULL,
`DDATA` date NOT NULL,
`CCATRIS` int(4) NOT NULL,
`NQUANT` int(4) NOT NULL,
`CAZIENDA` int(4) NOT NULL,
`CAFFILIATO` int(4) NOT NULL,
PRIMARY KEY (`ID`),
KEY `IDX_DDATA_PMS_DISPO` (`DDATA`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1084 ;
And "PMS_STAGIONI" is:
CREATE TABLE IF NOT EXISTS `PMS_STAGIONI` (
`CSTAGIONE` int(11) NOT NULL,
`NVALIDI` tinyint(2) NOT NULL,
`BECCEZIONE` tinyint(1) NOT NULL,
`AGGSET` varchar(7) DEFAULT NULL,
`DINIZVAL` date NOT NULL,
`DFINEVAL` date NOT NULL,
`CAZIENDA` int(4) NOT NULL,
`ID` int(11) NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`ID`),
KEY `CSTAGIONE` (`CSTAGIONE`),
KEY `IDX_INIZFINEVAL_PMS_STAGIONI` (`DINIZVAL`,`DFINEVAL`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=4 ;
A query of this sort would normally be written as follows, with indexes just as you have them...
SELECT DISTINCT s.dinizval
, s.dfineval
, d.ddata
FROM pms_dispo d
JOIN pms_stagioni s
ON d.ddata BETWEEN s.dinizval AND s.dfineval

SQL grouping and counting, and try to avoid temporary and filesort

I have two tables with matches and users.
I'm trying to find the way to get the top countries playing matches, and I have this SQL:
select
distinct(user.country),
count(*) as counter
from matches
left join user on matches.user_id = user.id
where
matches.`date` between '2014-01-01' and '2014-03-15'
group by user.country
order by counter DESC
limit 10
The problem is that I'm getting "Using where; Using temporary; Using file sort" and the sql takes about 8s in a m3.medium RDS Amazon server (not bad one!)
I have user.country indexed. Both tables are InnoDB.
Any ideas to improve it ?
Tables:
CREATE TABLE `user` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`nick` varchar(32) DEFAULT NULL,
`email` varchar(128) DEFAULT NULL,
`password` varchar(40) DEFAULT NULL,
`country` char(2) DEFAULT '',
PRIMARY KEY (`id`),
KEY `country` (`country`),
) ENGINE=InnoDB AUTO_INCREMENT=254183 DEFAULT CHARSET=utf8;
CREATE TABLE `matches` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`user_id` int(11) unsigned DEFAULT NULL,
`date` datetime DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `user_id` (`user_id`),
KEY `date` (`date`)
) ENGINE=InnoDB AUTO_INCREMENT=2593195 DEFAULT CHARSET=utf8;
EXPLAIN gives:
+----+-------------+---------+--------+-----------------+---------+---------+----------------------------+---------+------------------------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+---------+--------+-----------------+---------+---------+----------------------------+---------+----------------------------------------------+
| 1 | SIMPLE | matches | ALL | date | NULL | NULL | NULL | 2386708 | Using where; Using temporary; Using filesort |
| 1 | SIMPLE | user | eq_ref | PRIMARY,country | PRIMARY | 4 | matches.user_id | 1 | NULL |
+----+-------------+---------+--------+-----------------+---------+---------+----------------------------+---------+----------------------------------------------+
EDIT: Changing to inner join:
+----+-------------+----------+-------+------------------------------+-----------------+---------+------------------+--------+----------------------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+----------+-------+------------------------------+-----------------+---------+------------------+--------+----------------------------------------------+
| 1 | SIMPLE | user | index | PRIMARY,country | country | 7 | NULL | 234262 | Using index; Using temporary; Using filesort |
| 1 | SIMPLE | matches | ref | user_id,date | user_id | 5 | user.id | 5 | Using where |
+----+-------------+----------+-------+------------------------------+-----------------+---------+------------------+--------+----------------------------------------------+