how to optimize this query in MYSQL? - mysql

mysql> explain select a.id,a.title from users c
-> straight_join iask a on c.id=a.uid
-> straight_join ianswer b on a.id=b.iaskid
->
-> where (c.last_check is null or b.created>c.last_check) and c.id in (1,2)
-> group by a.id;
+----+-------------+-------+-------+---------------------------+------------+---------+----------+------+----------------------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+-------+---------------------------+------------+---------+----------+------+----------------------------------------------+
| 1 | SIMPLE | c | range | PRIMARY,i_users_lastcheck | PRIMARY | 4 | NULL | 2 | Using where; Using temporary; Using filesort |
| 1 | SIMPLE | a | ref | PRIMARY,i_iask_uid | i_iask_uid | 4 | bbs.c.id | 2 | |
| 1 | SIMPLE | b | ALL | i_ianswer_iaskid | NULL | NULL | NULL | 17 | Using where; Using join buffer |
+----+-------------+-------+-------+---------------------------+------------+---------+----------+------+----------------------------------------------+
3 rows in set (0.00 sec)
change the above "in (1,2)" into "=1" will make it all using index:
mysql> explain select a.id,a.title from iask a
-> join ianswer b on a.id=b.iaskid
-> join users c on c.id=a.uid
-> where (c.last_check is null or b.created>c.last_check) and c.id=1
-> group by a.id;
+----+-------------+-------+-------+---------------------------+------------------+---------+----------+------+---------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+-------+---------------------------+------------------+---------+----------+------+---------------------------------+
| 1 | SIMPLE | c | const | PRIMARY,i_users_lastcheck | PRIMARY | 4 | const | 1 | Using temporary; Using filesort |
| 1 | SIMPLE | a | ref | PRIMARY,i_iask_uid | i_iask_uid | 4 | const | 1 | Using where |
| 1 | SIMPLE | b | ref | i_ianswer_iaskid | i_ianswer_iaskid | 4 | bbs.a.id | 8 | Using where |
+----+-------------+-------+-------+---------------------------+------------------+---------+----------+------+---------------------------------+
3 rows in set (0.00 sec)
table structure as follows:
mysql> show create table users;
+-------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| Table | Create Table
+-------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| users | CREATE TABLE `users` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`email` varchar(128) NOT NULL,
`password` varchar(32) NOT NULL,
`screen_name` varchar(64) DEFAULT NULL,
`reputation` int(10) unsigned NOT NULL DEFAULT '0',
`imtype` varchar(1) DEFAULT '0' COMMENT '0--email,1--gtalk,2--msn',
`last_check` datetime DEFAULT NULL,
`robotno` int(10) unsigned DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `u_users_email` (`email`),
UNIQUE KEY `u_users_screen_name` (`screen_name`),
KEY `i_users_lastcheck` (`last_check`),
KEY `i_users_imtype_robotno` (`imtype`,`robotno`)
) ENGINE=InnoDB AUTO_INCREMENT=31 DEFAULT CHARSET=utf8 |
+-------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
1 row in set (0.02 sec)
mysql> show create table iask;
+-------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| Table | Create Table
+-------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| iask | CREATE TABLE `iask` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`uid` int(10) unsigned NOT NULL,
`title` varchar(250) NOT NULL,
`body` text,
`tags` varchar(100) NOT NULL,
`views` int(10) unsigned DEFAULT '0',
`votes` int(10) unsigned DEFAULT '0',
`answer_id` int(10) unsigned DEFAULT NULL,
`created` datetime NOT NULL,
`keywords` text,
PRIMARY KEY (`id`),
KEY `i_iask_uid` (`uid`),
FULLTEXT KEY `keywords` (`keywords`)
) ENGINE=MyISAM AUTO_INCREMENT=3 DEFAULT CHARSET=utf8 |
+-------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
1 row in set (0.00 sec)
mysql> show create table ianswer;
+---------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Table | Create Table |
+---------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| ianswer | CREATE TABLE `ianswer` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`iaskid` int(10) unsigned NOT NULL,
`uid` int(10) unsigned DEFAULT NULL,
`body` text,
`votes` int(10) unsigned DEFAULT '0',
`created` datetime NOT NULL,
`anonymous` tinyint(1) NOT NULL DEFAULT '0',
PRIMARY KEY (`id`),
KEY `i_ianswer_iaskid` (`iaskid`)
) ENGINE=InnoDB AUTO_INCREMENT=20 DEFAULT CHARSET=utf8 |
+---------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)
mysql>

If you are asking how to make the first query use an index like the second query does, I suspect it is because there aren't enough rows in your table to justify two index lookups. The row count for a full table scan is 17 rows, so it thinks it is faster just to spin across the rows looking for two ids. If you are concerned, you could try populating the table to see if it still chooses a full table scan.

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? :)

Mysql, slow avg query

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.

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.

Mysql wrong index after InnoDB compression

I have 2 mysql servers (master and slave). I've enabled InnoDB compression on Slave, after that mysql sometimes chooses wrong index on query.
Explain on Master:
+----+-------------+-------+--------+---------------+---------+---------+-----------------------------+-----------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+--------+---------------+---------+---------+-----------------------------+-----------+-------------+
| 1 | SIMPLE | p | range | PRIMARY | PRIMARY | 8 | NULL | 112017572 | Using where |
| 1 | SIMPLE | l | eq_ref | PRIMARY | PRIMARY | 8 | p.loan_ID | 1 | NULL |
| 1 | SIMPLE | af | eq_ref | PRIMARY | PRIMARY | 8 | p.fromAccount_ID | 1 | Using where |
| 1 | SIMPLE | at | eq_ref | PRIMARY | PRIMARY | 8 | p.toAccount_ID | 1 | Using where |
+----+-------------+-------+--------+---------------+---------+---------+-----------------------------+-----------+-------------+
Explain on Slave:
+----+-------------+-------+--------+-------------------------------------------------------------------------------+--------------------+---------+-----------------------------+--------+----------------------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+--------+-------------------------------------------------------------------------------+--------------------+---------+-----------------------------+--------+----------------------------------------------+
| 1 | SIMPLE | l | index | PRIMARY | FK243910AAD869E6 | 9 | NULL | 804876 | Using index; Using temporary; Using filesort |
| 1 | SIMPLE | p | ref | PRIMARY,FK4BE7532292C5D482,FK4BE75322AE503A13,FK4BE75322382D11BC,POSTING_DATE | FK4BE75322382D11BC | 9 | l.ID | 101 | Using index condition; Using where |
| 1 | SIMPLE | af | eq_ref | PRIMARY | PRIMARY | 8 | p.fromAccount_ID | 1 | Using where |
| 1 | SIMPLE | at | eq_ref | PRIMARY | PRIMARY | 8 | p.toAccount_ID | 1 | Using where |
+----+-------------+-------+--------+-------------------------------------------------------------------------------+--------------------+---------+-----------------------------+--------+----------------------------------------------+
SELECT
p.ID AS 'payment_id',
p.loan_ID AS 'loan_id',
l.client_ID AS 'client_ID',
p.amount AS 'amount',
p.postingDate AS 'payment_date',
CASE
WHEN af.acc_type = 'POLCH' THEN 'wallet'
WHEN af.acc_type = 'PLTCH' THEN 'wallet'
WHEN af.acc_type = 'CNTT' THEN 'bank'
WHEN af.acc_type = 'CNT2' THEN 'bank'
WHEN af.acc_type = 'KONCH' THEN 'bank'
WHEN af.acc_type = 'KRDTM' THEN 'cash'
WHEN af.acc_type = 'LDRCH' THEN 'bank'
ELSE concat('UNKNOWN_',af.acc_type)
END AS 'payment_system_type',
af.description AS 'payment_system'
FROM Posting AS p
INNER JOIN Account AS af ON p.fromAccount_ID = af.ID
INNER JOIN Account AS at ON p.toAccount_ID = at.ID
INNER JOIN Loan AS l ON p.loan_id = l.ID
WHERE (
af.acc_type = 'KONCH'
OR af.acc_type = 'PLTCH'
OR af.acc_type = 'POLCH'
OR af.acc_type = 'KRDTM'
OR af.acc_type = 'LDRCH'
OR af.acc_type = 'CNT2'
OR af.acc_type = 'CNTT')
AND at.acc_type = 'ABON'
AND p.postingDate < DATE(now())
AND p.ID > 0
ORDER BY p.ID LIMIT 10000;
Loan - l
Posting - P
Master:
| Loan | CREATE TABLE `Loan` (
`ID` bigint(20) NOT NULL AUTO_INCREMENT,
`amount` decimal(19,4) DEFAULT NULL,
`amountToReturn` decimal(19,4) DEFAULT NULL,
`isGivenOut` bit(1) DEFAULT b'0',
`isPaid` bit(1) DEFAULT NULL,
`issueDate` datetime DEFAULT NULL,
`loanPeriod` int(11) DEFAULT NULL,
`productType` varchar(255) DEFAULT NULL,
`realPayDate` datetime DEFAULT NULL,
`client_ID` bigint(20) DEFAULT NULL,
`product_ID` bigint(20) DEFAULT NULL,
`givenOutDate` datetime DEFAULT NULL,
`isPaidByBank` bit(1) DEFAULT NULL,
`accountNumberNBKI` varchar(255) DEFAULT NULL,
`needManualProcessing` bit(1) DEFAULT NULL,
`isReverted` bit(1) DEFAULT b'0',
`showInNBCHReport` bit(1) DEFAULT b'1',
`stake` decimal(19,5) DEFAULT NULL,
`ignoreProlongation` bit(1) DEFAULT b'0',
`stakeAfter21` decimal(19,5) DEFAULT NULL,
`discount_id` bigint(20) DEFAULT NULL,
`showInEquifaxReport` bit(1) DEFAULT b'1',
`ignoreNbch` bit(1) DEFAULT b'0',
PRIMARY KEY (`ID`),
KEY `FK2439106EC0BA18` (`product_ID`),
KEY `ISPAID_INDEX` (`isPaid`) USING BTREE,
KEY `ISP_ISGOUT_INDEX` (`isPaid`,`isGivenOut`),
KEY `ISSUEDATE_INDEX` (`issueDate`),
KEY `FK243910735827C6` (`discount_id`),
KEY `idx_Loan_realPayDate` (`realPayDate`),
KEY `idx_Loan_givenOutDate` (`givenOutDate`),
KEY `FK243910AAD869E6` (`client_ID`),
CONSTRAINT `_FK243910735827C6` FOREIGN KEY (`discount_id`) REFERENCES `Discount` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2000623399 DEFAULT CHARSET=utf8
Posting | CREATE TABLE `Posting` (
`ID` bigint(20) NOT NULL AUTO_INCREMENT,
`amount` decimal(19,4) DEFAULT NULL,
`postingDate` datetime DEFAULT NULL,
`fromAccount_ID` bigint(20) DEFAULT NULL,
`loan_ID` bigint(20) DEFAULT NULL,
`toAccount_ID` bigint(20) DEFAULT NULL,
`sourceType` varchar(255) DEFAULT NULL,
PRIMARY KEY (`ID`),
KEY `FK4BE7532292C5D482` (`fromAccount_ID`),
KEY `FK4BE75322AE503A13` (`toAccount_ID`),
KEY `FK4BE75322382D11BC` (`loan_ID`),
KEY `POSTING_DATE` (`postingDate`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=230996702 DEFAULT CHARSET=utf8
Slave:
| Loan | CREATE TABLE `Loan` (
`ID` bigint(20) NOT NULL AUTO_INCREMENT,
`amount` decimal(19,4) DEFAULT NULL,
`amountToReturn` decimal(19,4) DEFAULT NULL,
`isGivenOut` bit(1) DEFAULT b'0',
`isPaid` bit(1) DEFAULT NULL,
`issueDate` datetime DEFAULT NULL,
`loanPeriod` int(11) DEFAULT NULL,
`productType` varchar(255) DEFAULT NULL,
`realPayDate` datetime DEFAULT NULL,
`client_ID` bigint(20) DEFAULT NULL,
`product_ID` bigint(20) DEFAULT NULL,
`givenOutDate` datetime DEFAULT NULL,
`isPaidByBank` bit(1) DEFAULT NULL,
`accountNumberNBKI` varchar(255) DEFAULT NULL,
`needManualProcessing` bit(1) DEFAULT NULL,
`isReverted` bit(1) DEFAULT b'0',
`showInNBCHReport` bit(1) DEFAULT b'1',
`stake` decimal(19,5) DEFAULT NULL,
`ignoreProlongation` bit(1) DEFAULT b'0',
`stakeAfter21` decimal(19,5) DEFAULT NULL,
`discount_id` bigint(20) DEFAULT NULL,
`showInEquifaxReport` bit(1) DEFAULT b'1',
`ignoreNbch` bit(1) DEFAULT b'0',
PRIMARY KEY (`ID`),
KEY `FK2439106EC0BA18` (`product_ID`),
KEY `ISPAID_INDEX` (`isPaid`) USING BTREE,
KEY `ISP_ISGOUT_INDEX` (`isPaid`,`isGivenOut`),
KEY `ISSUEDATE_INDEX` (`issueDate`),
KEY `FK243910735827C6` (`discount_id`),
KEY `idx_Loan_realPayDate` (`realPayDate`),
KEY `idx_Loan_givenOutDate` (`givenOutDate`),
KEY `FK243910AAD869E6` (`client_ID`),
CONSTRAINT `_FK243910735827C6` FOREIGN KEY (`discount_id`) REFERENCES `Discount` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2000623399 DEFAULT CHARSET=utf8
ROW_FORMAT=COMPRESSED KEY_BLOCK_SIZE=4
Posting | CREATE TABLE `Posting` (
`ID` bigint(20) NOT NULL AUTO_INCREMENT,
`amount` decimal(19,4) DEFAULT NULL,
`postingDate` datetime DEFAULT NULL,
`fromAccount_ID` bigint(20) DEFAULT NULL,
`loan_ID` bigint(20) DEFAULT NULL,
`toAccount_ID` bigint(20) DEFAULT NULL,
`sourceType` varchar(255) DEFAULT NULL,
PRIMARY KEY (`ID`),
KEY `FK4BE7532292C5D482` (`fromAccount_ID`),
KEY `FK4BE75322AE503A13` (`toAccount_ID`),
KEY `FK4BE75322382D11BC` (`loan_ID`),
KEY `POSTING_DATE` (`postingDate`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=230996702 DEFAULT CHARSET=utf8 ROW_FORMAT=COMPRESSED KEY_BLOCK_SIZE=4
If remove table Loan from Query
+----+-------------+-------+--------+------------------------------------------------------------+---------+---------+-----------------------------+-----------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+--------+------------------------------------------------------------+---------+---------+-----------------------------+-----------+-------------+
| 1 | SIMPLE | p | range | PRIMARY,FK4BE7532292C5D482,FK4BE75322AE503A13,POSTING_DATE | PRIMARY | 8 | NULL | 107736559 | Using where |
| 1 | SIMPLE | af | eq_ref | PRIMARY | PRIMARY | 8 | smsfinance.p.fromAccount_ID | 1 | Using where |
| 1 | SIMPLE | at | eq_ref | PRIMARY | PRIMARY | 8 | smsfinance.p.toAccount_ID | 1 | Using where |
+----+-------------+-------+--------+------------------------------------------------------------+---------+---------+-----------------------------+-----------+-------------+
If I add
create index acc on Account(acc_type);
Plan:
+----+-------------+-------+--------+-------------------------------------------------------------------------------+--------------------+---------+---------------------------+------+--------------------------------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+--------+-------------------------------------------------------------------------------+--------------------+---------+---------------------------+------+--------------------------------------------------------+
| 1 | SIMPLE | af | range | PRIMARY,acc | acc | 21 | NULL | 4192 | Using index condition; Using temporary; Using filesort |
| 1 | SIMPLE | p | ref | PRIMARY,FK4BE7532292C5D482,FK4BE75322AE503A13,FK4BE75322382D11BC,POSTING_DATE | FK4BE7532292C5D482 | 9 | smsfinance.af.ID | 54 | Using index condition; Using where |
| 1 | SIMPLE | l | eq_ref | PRIMARY | PRIMARY | 8 | smsfinance.p.loan_ID | 1 | NULL |
| 1 | SIMPLE | at | eq_ref | PRIMARY,acc | PRIMARY | 8 | smsfinance.p.toAccount_ID | 1 | Using where |
+----+-------------+-------+--------+-------------------------------------------------------------------------------+--------------------+---------+---------------------------+------+--------------------------------------------------------+
Query execute long time.
I suspect compression leads to different statistics, which can lead to different execution plans.
I see no use for the table Loan in this query. Removing it from the query may force the explain plans to be the same.
You have 200M rows in each table? Another speedup would be to shrink the tables.
Change BIGINT (8 bytes each) to INT UNSIGNED (4 bytes, range 0..4 billion) wherever possible.
Normalize the _type columns, replacing with SMALLINT UNSIGNED (2 bytes, range 0..64K) or other suitable integer type. Or turn the column into an ENUM if there are a finite, small number, of "types".
Does Account have INDEX(acc_type)? (Or at least starting with acc_type.)
Remove INNER JOIN Loan AS l ON p.loan_id = l.ID and replace l.client_ID AS 'client_ID', with
( SELECT client_ID FROM Loans WHERE ID = p.loan_id ) AS 'client_ID',
I think this will force a different query plan, perhaps a good one.

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 |
+----+-------------+----------+-------+------------------------------+-----------------+---------+------------------+--------+----------------------------------------------+