【问题标题】:MySQL - joining tables on same table multiple times with different conditions takes foreverMySQL - 在不同条件下多次连接同一张表上的表需要很长时间
【发布时间】:2012-03-21 11:29:28
【问题描述】:

简单来说,我有四个表。

ref_TagGroup (top-level descriptive containers for various tags)
ref_Tag (tags with name and unique tagIDs)
ref_Product
ref_TagMap (TagID,Container,ContainerType)
A fifth table, ref_ProductFamily exists but is not directly part of this query.

我使用 ref_TagMap 表将标签映射到产品,同时也将标签映射到 TagGroups 以及产品系列。 ContainerType 相应地设置为 PROD/TAGGROUP/PRODFAM。

所以,我想返回标签组、标签名以及标签映射到的产品和产品系列的数量......所以结果如下:

组名 |标记名 | TagHitCnt

我的问题是,为什么第一个查询以毫秒为单位返回,第二个查询以毫秒为单位返回,但第三个查询(只是添加一个“或”条件以包括标签到产品和标签到家庭映射)需要很长时间(好吧,反正超过十分钟......我还没有让它运行一整夜。)

问题 1:

SELECT ref_taggroup.groupname,ref_tag.tagname,COUNT(DISTINCT IFNULL(ref_product.familyid,ref_product.id + 100000000),ref_product.name) AS 'taghitcnt' 
FROM (ref_taggroup,ref_tag,ref_product) 
LEFT JOIN ref_tagmap GROUPMAP ON GROUPMAP.containerid=ref_taggroup.groupid 
LEFT JOIN ref_tagmap PRODMAP ON PRODMAP.containerid=ref_product.id 
WHERE 
GROUPMAP.tagid=ref_tag.tagid AND GROUPMAP.containertype='TAGGROUP' 
AND
PRODMAP.tagid=ref_tag.tagid AND PRODMAP.containertype='PROD' 
GROUP BY tagname 
ORDER BY groupname,tagname ;

问题 2:

SELECT ref_taggroup.groupname,ref_tag.tagname,COUNT(DISTINCT IFNULL(ref_product.familyid,ref_product.id + 100000000),ref_product.name) AS 'taghitcnt' 
FROM (ref_taggroup,ref_tag,ref_product) 
LEFT JOIN ref_tagmap GROUPMAP ON GROUPMAP.containerid=ref_taggroup.groupid 
LEFT JOIN ref_tagmap PRODFAMMAP ON PRODFAMMAP.containerid=ref_product.familyid 
WHERE 
GROUPMAP.tagid=ref_tag.tagid AND GROUPMAP.containertype='TAGGROUP' 
AND
PRODFAMMAP.tagid=ref_tag.tagid AND PRODFAMMAP.containertype='PRODFAM' 
GROUP BY tagname 
ORDER BY groupname,tagname ;

问题 3:

SELECT ref_taggroup.groupname,ref_tag.tagname,COUNT(DISTINCT IFNULL(ref_product.familyid,ref_product.id + 100000000),ref_product.name) AS 'taghitcnt' 
FROM (ref_taggroup,ref_tag,ref_product) 
LEFT JOIN ref_tagmap GROUPMAP ON GROUPMAP.containerid=ref_taggroup.groupid 
JOIN ref_tagmap PRODMAP ON PRODMAP.containerid=ref_product.id 
JOIN ref_tagmap PRODFAMMAP ON PRODFAMMAP.containerid=ref_product.familyid 
WHERE 
GROUPMAP.tagid=ref_tag.tagid AND GROUPMAP.containertype='TAGGROUP' 
AND
((PRODMAP.tagid=ref_tag.tagid AND PRODMAP.containertype='PROD') 
OR 
(PRODFAMMAP.tagid=ref_tag.tagid AND PRODFAMMAP.containertype='PRODFAM' ))
GROUP BY tagname 
ORDER BY groupname,tagname ;

-- 为了回答可能出现的问题,选择中的 COUNT Distinct ifnull 旨在为分组到系列中的大量产品返回一条记录,为不在系列中的每个“独立”产品返回一条记录.此代码在其他查询中运行良好。

我已尝试对前两个查询执行 UNION,它有效并且很快返回,但由于其他原因不实用,我不会在这里讨论。

最好的方法是什么?我做错了什么?

谢谢!


添加解释输出

QUERY1                                  
id  select_type table   type    possible_keys   key key_len ref rows    Extra
1   SIMPLE  GROUPMAP    ALL                 5640    Using where; Using temporary; Using filesort
1   SIMPLE  ref_tag ref PRIMARY PRIMARY 4   lsslave01.GROUPMAP.tagid    1   Using index
1   SIMPLE  ref_taggroup    ref PRIMARY PRIMARY 4   lsslave01.GROUPMAP.containerid  3   Using index
1   SIMPLE  PRODMAP ALL                 5640    Using where; Using join buffer
1   SIMPLE  ref_product eq_ref  PRIMARY PRIMARY 4   lsslave01.PRODMAP.containerid   1   

QUERY2                                  
id  select_type table   type    possible_keys   key key_len ref rows    Extra
1   SIMPLE  GROUPMAP    ALL                 5640    Using where; Using temporary; Using filesort
1   SIMPLE  ref_tag ref PRIMARY PRIMARY 4   lsslave01.GROUPMAP.tagid    1   Using index
1   SIMPLE  ref_taggroup    ref PRIMARY PRIMARY 4   lsslave01.GROUPMAP.containerid  3   Using index
1   SIMPLE  PRODFAMMAP  ALL                 5640    Using where; Using join buffer
1   SIMPLE  ref_product ref FixtureType FixtureType 5   lsslave01.PRODFAMMAP.containerid    39  Using where

QUERY3                                  
id  select_type table   type    possible_keys   key key_len ref rows    Extra
1   SIMPLE  GROUPMAP    ALL                 5640    Using where; Using temporary; Using filesort
1   SIMPLE  ref_tag ref PRIMARY PRIMARY 4   lsslave01.GROUPMAP.tagid    1   Using index
1   SIMPLE  ref_taggroup    ref PRIMARY PRIMARY 4   lsslave01.GROUPMAP.containerid  3   Using index
1   SIMPLE  PRODMAP ALL                 5640    Using join buffer
1   SIMPLE  PRODFAMMAP  ALL                 5640    Using where; Using join buffer
1   SIMPLE  ref_product eq_ref  PRIMARY,FixtureType PRIMARY 4   lsslave01.PRODMAP.containerid   1   Using where

enter code here

为感兴趣的人提供一个更新: 我终于让上面的第三个查询运行完成。它花了大约1000秒。将此时间除以每个查询(1 或 2)运行所需的时间,我们得到一个大约 6000 的数字......这非常接近我们在开发环境中使用的 ref_tagmap 表的大小(生产量大得多)。所以,看起来我们正在针对该表中的每条记录运行一个查询......但我仍然不明白为什么。

任何帮助将不胜感激......我的意思是认真,非常感谢。

【问题讨论】:

  • 在每个查询前加上EXPLAIN 会得到什么?你能告诉我们CREATE TABLE 的输出吗?为什么要混合连接语法?
  • 我为三个查询中的每一个附加了解释输出。您需要为哪些表创建?它们非常简单......我认为问题在于我如何在第三个查询中使用“OR”子句......那里有任何指导吗?至于 LEFT JOINS 和 JOINS 的用户,这就是我最终的结果……我尝试了多种组合。当然,我不是 sql 天才。
  • 尝试将 Query3 重写为 Union。
  • 索引连接条件中使用的列
  • ypercube,我已经做到了,它按预期加快了速度,但由于解释的其他原因,我不能在这段代码中使用联合。

标签: mysql join multiple-tables


【解决方案1】:

这与其说是一个“答案”,不如说是一些观察/建议。

首先,我很好奇您是否可以对整数 ID 而不是标签名称进行 GROUP BY?我将更改 ref_TagMap.containertype 字段以保存代表 TAGGROUP、PROD 和 PRODFAM 的三个可能值的 tinyint 枚举值。索引的 tinyint 字段应该比字符串值的索引稍快。不过,它可能不会有太大的不同,因为它是连接子句中的第二个条件,而且索引值的分布并没有那么大。

接下来是观察/提醒,当 OR 语句的前半部分经常评估为 FALSE 时,您正在让 MySQL 每次都评估条件的两半。因此,您希望将最有可能评估为 TRUE 的条件放在首位(也就是在 OR 之前)。

我怀疑这两个问题中的任何一个都是您真正的问题......尽管第二段中的问题可能会起作用。 似乎查询 3 的高性能版本的最快方法可能是简单地用前两个查询的结果填充一个临时表,然后从该临时表中提取你正在寻找的结果第三个。也许这样做你会发现为什么第三个查询这么慢。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-06-05
    • 2014-01-05
    • 1970-01-01
    • 2011-10-18
    • 1970-01-01
    • 2021-09-16
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多