MySQL查询在JOIN中查找没有匹配记录的项目非常慢

时间:2013-05-28 17:13:47

标签: mysql query-optimization

我已经阅读了很多关于查询优化的问题,但没有人帮我解决这个问题。

作为设置,我有3个表代表"条目"可以有零个或多个"类别"。

> show create table entries;
CREATE TABLE `entries` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT
  ...
  `name` varchar(255),
  `updated_at` timestamp NOT NULL,
  ...
  PRIMARY KEY (`id`),
  KEY `name` (`name`)
) ENGINE=InnoDB

> show create table entry_categories;
CREATE TABLE `entry_categories` (
  `ent_name` varchar(255),
  `cat_id` int(11),
  PRIMARY KEY (`ent_name`,`cat_id`),
  KEY `names` (`ent_name`)
) ENGINE=InnoDB

(实际"类别"表格不会出现问题。)

编辑"条目"在应用程序中,在条目表中创建一个新行 - 就像维基页面的历史记录一样 - 具有相同的名称和更新的时间戳。我想看看有多少具有独特名字的参赛作品没有类别,这看起来非常简单:

SELECT COUNT(id)
FROM entries e
LEFT JOIN entry_categories c
ON e.name=c.ent_name
WHERE c.ent_name IS NUL
GROUP BY e.name;

在我的小数据集上(大约6000个条目,大约有4000个名称,每个命名条目平均大约一个类别),此查询需要超过24秒(!)。我也试过了

SELECT COUNT(id)
FROM entries e
WHERE NOT EXISTS(
  SELECT ent_name
  FROM entry_categories c
  WHERE c.ent_name = e.name
)
GROUP BY e.name;

有类似的结果。这对我来说似乎非常非常缓慢,特别是考虑到使用

查找单个类别中的条目
SELECT COUNT(*)
FROM entries e
JOIN (
  SELECT ent_name as name
  FROM entry_categories
  WHERE cat_id = 123
)c
USING (name)
GROUP BY name;

在相同的数据上运行大约120毫秒。是否有更好的方法可以在表中找到在另一个表中至少有一个相应条目的记录?


我会尝试为每个查询转录EXPLAIN结果:

> EXPLAIN {no category query};
+----+-------------+-------+-------+---------------+-------+---------+------+------+----------------------------------------------+
| id | select_type | table | type  | possible_keys |  key  | key_len | ref  | rows |                    Extra                     |
+----+-------------+-------+-------+---------------+-------+---------+------+------+----------------------------------------------+
|  1 | SIMPLE      | e     | index | NULL          | name  |     767 | NULL | 6222 | Using index; Using temporary; Using filesort |
|  1 | SIMPLE      | c     | index | PRIMARY,names | names |     767 | NULL | 6906 | Using where; using index; Not exists         |
+----+-------------+-------+-------+---------------+-------+---------+------+------+----------------------------------------------+

> EXPLAIN {single category query}
+----+-------------+------------+-------+---------------+-------+---------+------+--------------------------+---------------------------------+
| id | select_type |   table    | type  | possible_keys |  key  | key_len | ref  |           rows           |              Extra              |
+----+-------------+------------+-------+---------------+-------+---------+------+--------------------------+---------------------------------+
|  1 | PRIMARY     | <derived2> | ALL   | NULL          | NULL  | NULL    | NULL | 2850                     | Using temporary; Using filesort |
|  1 | PRIMARY     | e          | ref   | name          | 767   | c.name  | 1    | Using where; Using index |                                 |
|  2 | DERIVED     | c          | index | NULL          | names | NULL    | 6906 | Using where; Using index |                                 |
+----+-------------+------------+-------+---------------+-------+---------+------+--------------------------+---------------------------------+

2 个答案:

答案 0 :(得分:1)

尝试:

select name, sum(e) count_entries from 
(select name, 1 e, 0 c from entries 
 union all 
 select ent_name name, 0 e, 1 c from entry_categories) s 
group by name 
having sum(c) = 0

答案 1 :(得分:0)

首先:删除names密钥,因为它与主密钥相同(因为ent_name列是主键的最左侧,PK可用于解析查询) 。这应该通过在连接中使用PK来改变explain的输出。

您用来加入的密钥非常大(255 varchar列) - 如果您可以使用整数,那就更好了,即使这意味着再引入一个表(使用room_id,room_name映射)

由于某些原因,查询使用filesort,尽管您没有order by子句。

您是否可以在每个查询旁边显示解释结果,以及单个类别查询,以便进一步诊断?