MySQL:如何使用“JOIN”和“order_by”加速“Count()”查询?

时间:2011-08-21 05:17:59

标签: mysql indexing

我的MySQL数据库中有以下两个(为了示例而简化)表:

DESCRIBE appname_item;

-----------------+---------------+------+-----+---------+----------------+
| Field           | Type          | Null | Key | Default | Extra          |
+-----------------+---------------+------+-----+---------+----------------+
| id              | int(11)       | NO   | PRI | NULL    | auto_increment |
| name            | varchar(200)  | NO   |     | NULL    |                |
+-----------------+---------------+------+-----+---------+----------------+

DESCRIBE appname_favorite;

+---------------+----------+------+-----+---------+----------------+
| Field         | Type     | Null | Key | Default | Extra          |
+---------------+----------+------+-----+---------+----------------+
| id            | int(11)  | NO   | PRI | NULL    | auto_increment |
| user_id       | int(11)  | NO   | MUL | NULL    |                |
| item_id       | int(11)  | NO   | MUL | NULL    |                |
+---------------+----------+------+-----+---------+----------------+

我正在尝试获取按收藏夹数量排序的项目列表。下面的查询有效,但Item表中有数千条记录,查询最多需要几分钟才能完成。

SELECT `appname_item`.`id`, `appname_item`.`name`, COUNT(`appname_favorite`.`id`) AS `num_favorites` 
FROM `appname_item` 
LEFT OUTER JOIN `appname_favorite` ON (`appname_item`.`id` = `appname_favorite`.`item_id`) 
GROUP BY `appname_item`.`id`, `appname_item`.`name` 
ORDER BY `num_favorites` DESC;

以下是EXPLAIN的结果,它提供了一些有关查询速度如此之慢的信息(类型为“ALL”,“使用临时”和“使用filesort”应该尽可能避免。)

+----+-------------+--------------------+------+-----------------------------+-----------------------------+---------+-------------------------------+------+---------------------------------+
| id | select_type | table              | type | possible_keys               | key                         | key_len | ref                           | rows | Extra                           |
+----+-------------+--------------------+------+-----------------------------+-----------------------------+---------+-------------------------------+------+---------------------------------+
|  1 | SIMPLE      | appname_item       | ALL  | NULL                        | NULL                        | NULL    | NULL                          |  574 | Using temporary; Using filesort |
|  1 | SIMPLE      | appname_favorite   | ref  | appname_favorite_67b70d25   | appname_favorite_67b70d25   | 4       | appname.appname_item.id       |    1 |                                 |
+----+-------------+--------------------+------+-----------------------------+-----------------------------+---------+-------------------------------+------+---------------------------------+

我知道优化查询的最简单方法是添加一个索引,但我似乎无法弄清楚如何为涉及JOIN和order_by的Count()查询添加索引。我还应该提一下,我通过Django ORM运行它,所以更愿意不更改sql查询,只是修复和微调数据库以最有效的方式运行查询。

我一直试图解决这个问题,所以任何帮助都会非常感激!

更新

以下是db中已有的索引:

+--------------------+------------+-----------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+
| Table              | Non_unique | Key_name                    | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment |
+--------------------+------------+-----------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+
| appname_favorite   |          0 | PRIMARY                     |            1 | id          | A         |         594 |     NULL | NULL   |      | BTREE      |         |
| appname_favorite   |          1 | appname_favorite_fbfc09f1   |            1 | user_id     | A         |          12 |     NULL | NULL   |      | BTREE      |         |
| appname_favorite   |          1 | appname_favorite_67b70d25   |            1 | item_id     | A         |         594 |     NULL | NULL   |      | BTREE      |         |
+--------------------+------------+-----------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+

3 个答案:

答案 0 :(得分:2)

实际上你无法避免使用filesort,因为计数是在计算时确定的,并且在索引中是未知的。我能想象的唯一解决方案是为表 appname_item 创建一个复合索引,根据您的特定数据,这可能有所帮助或有所帮助:

ALTER TABLE appname_item ADD UNIQUE INDEX `item_id_name` (`id` ASC, `name` ASC);

答案 1 :(得分:1)

您的查询没有任何问题 - 看起来不错。

可能是优化器有关于该表的过时信息。试试这个:

ANALYZE TABLE <tableaname>;

所涉及的所有表格。

答案 2 :(得分:0)

首先,对于count()函数,您可以查看此答案以了解更多详细信息: https://stackoverflow.com/a/2710630/1020600

  

例如,使用MySQL,count(*)将在MyISAM表下快速完成   但在InnoDB下运行缓慢。在InnoDB下你应该使用count(1)或   计数(PK)

如果你的存储引擎是MYISAM,如果你想依靠行(我猜是这样),使用count(*)就足够了。

从你的EXPLAIN中,我发现appname_item没有密钥,如果我尝试添加条件

where `appname_item`.`id` = `appname_favorite`.`item_id`

然后出现“钥匙”。这很有趣但是很有效。

这样的最终sql

explain SELECT `appname_item`.`id`, `appname_item`.`name`, COUNT(*) AS `num_favorites` 
FROM `appname_item` 
LEFT OUTER JOIN `appname_favorite` ON (`appname_item`.`id` = `appname_favorite`.`item_id`) 
where `appname_item`.`id` = `appname_favorite`.`item_id`
GROUP BY `appname_item`.`id`, `appname_item`.`name` 
ORDER BY `num_favorites` DESC;
  

+ ---- + ------------- + ------------------ + -------- + --------------- + --------- + --------- + ------------- ------------------ + ------ + ------------------------ ---------------------- + | id | select_type |表|类型| possible_keys |关键
  | key_len | ref |行|额外
  |   + ---- + ------------- + ------------------ + -------- + - ------------- + --------- + --------- + ---------------- --------------- + ------ + --------------------------- ------------------- + | 1 |简单| appname_favorite |指数| item_id |   item_id | 5 | NULL | 2312 |运用   指数;使用临时;使用filesort | | 1 |简单|   appname_item | eq_ref |主要|主要| 4 |   test.appname_favorite.item_id | 1 |使用何处   |   + ---- + ------------- + ------------------ + -------- + - ------------- + --------- + --------- + ---------------- --------------- + ------ + --------------------------- ------------------- +

在我的计算机上,表appname_item有1686行,appname_favorite有2312行,旧sql需要15到23ms。新的sql需要3.7到5.3毫秒