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

在JOIN中查找没有匹配记录的项目的MySQL查询速度非常慢,mysql,query-optimization,Mysql,Query Optimization,我读过很多关于查询优化的问题,但没有一个能帮到我 作为设置,我有3个表,它们表示一个“条目”,可以有零个或多个“类别” (实际的“类别”表不成问题。) 在应用程序中编辑“条目”会在条目表中创建一个新行——就像wiki页面的历史记录一样——具有相同的名称和更新的时间戳。我想看看有多少唯一命名的条目没有类别,这似乎非常简单: SELECT COUNT(id) FROM entries e LEFT JOIN entry_categories c ON e.name=c.ent_name WHERE

我读过很多关于查询优化的问题,但没有一个能帮到我

作为设置,我有3个表,它们表示一个“条目”,可以有零个或多个“类别”

(实际的“类别”表不成问题。)

在应用程序中编辑“条目”会在条目表中创建一个新行——就像wiki页面的历史记录一样——具有相同的名称和更新的时间戳。我想看看有多少唯一命名的条目没有类别,这似乎非常简单:

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 {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 |                                 |
+----+-------------+------------+-------+---------------+-------+---------+------+--------------------------+---------------------------------+
>解释{无类别查询};
+----+-------------+-------+-------+---------------+-------+---------+------+------+----------------------------------------------+
|id |选择|类型|类型|可能的|键|键|列|参考|行|额外|
+----+-------------+-------+-------+---------------+-------+---------+------+------+----------------------------------------------+
|1 |简单| e |索引|空|名称| 767 |空| 6222 |使用索引;使用临时设备;使用文件排序|
|1 |简单| c |索引|主要,名称|名称| 767 |空| 6906 |使用where;使用指数;不存在|
+----+-------------+-------+-------+---------------+-------+---------+------+------+----------------------------------------------+
>解释{单一类别查询}
+----+-------------+------------+-------+---------------+-------+---------+------+--------------------------+---------------------------------+
|id |选择|类型|类型|可能的|键|键|列|参考|行|额外|
+----+-------------+------------+-------+---------------+-------+---------+------+--------------------------+---------------------------------+
|1 | PRIMARY | | ALL | NULL | NULL | NULL | NULL | NULL | 2850 |使用临时命令;使用文件排序|
|1 | PRIMARY | e | ref | name | 767 | c.name | 1 |使用where;使用指数||
|2 |派生| c |索引| NULL |名称| NULL | 6906 |使用where;使用指数||
+----+-------------+------------+-------+---------------+-------+---------+------+--------------------------+---------------------------------+

首先:删除
名称
键,因为它与主键相同(因为
ent\u name
列是主键中最左边的,PK可用于解析查询)。这应该通过在连接中使用PK来更改explain的输出

用于连接的键非常大(255个varchar列)-如果可以使用整数,则效果更好,即使这意味着要引入一个表(使用room\u id、room\u name映射)

出于某种原因,查询使用了
filesort
,尽管没有
orderby
子句

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

尝试:

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

为什么要使用条目名称而不是条目ID来标识entry_categories表中的条目?请将“entry”表行视为wiki页面的单独修订版——页面名称是用户关心的ID,但我们需要保留更改的历史记录。有一个条目ID,但类别应用于“页面”,而不是一个修订版。以下查询需要多长时间:
select name,sum(e)from(select name,1e,0c from entries union all select ent_name,0e,1c from entry_categories)s group by name,sum(c)=0?@MarkBannister这是1.37秒。我对UNION和HAVE有点模糊,但如果我读对了,它将创建一个表(条目名称,e=1,c=0),并将其与一个单独的表(ent_名称,e=0,c=1)合并,结果是(条目名称,具有该名称的条目表行数,具有该名称的条目类别行数)。对吗?如果是这样,这就解决了我的问题!你可以回答,我会接受的。编码者:是的-我已经添加了这个查询作为答案。我尝试了一个模糊相似的外部联接,希望为每个条目获得一个名称加category\u id的表,category\u id在没有category的行中为null。这花了超过2分钟的时间。你们的联盟需要1.3秒,如果我只想数一数的话,至少需要200秒。我不知道为什么,但是哇,真是不同@编码器:很高兴我能帮上忙——我怀疑它是在做表扫描,而不是使用索引(索引在较小的表上更有效),但我不确定;查询计划可以提供进一步的见解。
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