【问题标题】:Slow MySQL IN query -- how to convert to JOINs?缓慢的 MySQL IN 查询——如何转换为 JOIN?
【发布时间】:2012-05-10 09:29:22
【问题描述】:

我目前在我的应用程序中运行当前的 MySQL 查询最多需要 10 秒:

SELECT tagid, tag FROM tags WHERE tagid IN 
(SELECT DISTINCT tagid FROM news_tags WHERE newsid IN 
(SELECT newsid FROM news_tags WHERE tagid IN (16,32)
GROUP BY newsid HAVING COUNT(newsid)>=2)) 
AND tagid NOT IN (16,32) ORDER BY level, tagid

使用的表格是:

  • news_tags,列newsidtagid
  • tags,列tagidtaglevel

查询的目的是找到已经被标记为tagid 16 32的标签的“新闻”项目,然后找到这些新闻项目也被标记的其他标签,例如允许用户使用更具体的标签组合进一步缩小“新闻”项目的范围。最终目标是从tags 表中获取剩余的相关tagtagid 列。

我在等效的JOIN 上尝试了不同的尝试,但未能在附加了提供的标签的新闻项目上选择所有剩余的tagids。

这是我的EXPLAIN SQL 结果,以防它们指出我遗漏的另一个缓慢原因:

id|select_type |table |type |possible_keys|key |key_len|ref |rows|Extra 1|PRIMARY |标签 |范围 |PRIMARY |PRIMARY| 4|空| 55|在哪里使用;使用文件排序 2|相关子查询|news_tags|index_subquery|tagid |tagid | 4|功能| 26|使用索引;使用哪里 3|相关子查询|news_tags|index |tagid |PRIMARY| 8|空| 11|在哪里使用;使用索引

只是为了澄清问题:我想要用 16 和 32 标记的新闻项目的剩余标记,而不是 16 或 32。抱歉有任何混淆。

【问题讨论】:

    标签: mysql sql database join


    【解决方案1】:

    编辑:我的查询严格基于提供的 sql OP,只是想按照问题标题中的要求加快查询速度。

    SELECT DISTINCT t.tagid, t.tag FROM tags AS t
    JOIN            news_tags AS nt1 USING (tagid) 
    JOIN            news_tags AS nt2 USING (newsid)
    WHERE           nt2.tagid IN (16, 32) AND t.tagid NOT IN (16, 32) 
    GROUP BY        nt2.newsid HAVING COUNT(nt2.newsid)>=2
    ORDER BY        t.level, t.tagid
    

    【讨论】:

      【解决方案2】:

      我最终想出了一个快速查询,使用 JOINS 而不是 IN 语句解决了这个问题:

      SELECT tags.tagid,tags.tag FROM tags 
      INNER JOIN (SELECT DISTINCT news_tags.tagid FROM news_tags
      INNER JOIN (SELECT newsid FROM news_tags WHERE tagid IN (16,32) 
      GROUP BY newsid HAVING count(newsid) >= 2) tagged_news 
      ON news_tags.newsid = tagged_news.newsid 
      WHERE news_tags.tagid NOT IN (16,32)) rem_tags
      ON tags.tagid = rem_tags.tagid
      ORDER BY level, tagid
      

      这显然不如eggyal的解决方案干净或优雅,所以我最终在我的应用程序中采用了他的解决方案。

      我很想听听更多客观原因(除了优雅),为什么 eggyval 的解决方案比上述 SQL 语句更受青睐,既可以为问题找到最佳 SQL 语句,也可以为未来学习。感谢到目前为止所有的帮助。

      【讨论】:

      • 我认为您的解决方案将匹配包含标签 16 32 的文章,前提是它们还包含至少一个其他标签。
      • eggyval,我查了一下是不是这样,但我不相信。子查询“SELECT newsid FROM news_tags WHERE tagid IN (16,32) GROUP BY newsid HAVING count(newsid) >= 2”只抓取标签为 16 和 32 的 newssid,因为只有 newsid 的两行输出针对 where 条件 'tagid IN (16,32)'(而不是其他标签)被保留。如果我遗漏了什么,请告诉我,但我观察了结果,这就是我所看到的。
      • 用带有(1,2,3,16)标签的新闻文章进行测试。
      • 在@eggyal 的回答下的一个cmets 中,您说BOTH tagid 16 and 32, not either,但在您的查询中,您仍然使用了IN。如果确定它是您想要的AND,只需避开INGROUP BY,这不仅速度较慢,而且具有不同的含义。
      • 在您的情况下,您的查询可能有效,但这就是因为至少有两个带有 tagid 16 或 32 的 newssid,或者 [tagid 16 和 32 至少有一个 newssid - 这就是您的想]。想一想新闻标签中没有 tagid 16 并且新闻标签中有 2 个或更多记录 tagid 为 32 的情况。您的查询仍会返回该内部查询的记录,就您的文字描述而言,这不是您想要的。
      【解决方案3】:
      SELECT DISTINCT tags.tagid, tags.tag
      FROM
             tags                             -- tags from the ...
        JOIN news_tags AS n0 USING (tagid)    -- ... news items tagged with ...
        JOIN news_tags AS n1 USING (newsid)   -- ... tagid = 16 and ...
        JOIN news_tags AS n2 USING (newsid)   -- ... tagid = 32
      WHERE
        n1.tagid = 16 AND n2.tagid = 32
        AND tags.tagid NOT IN (16,32)         -- not the tags we already know about
      ORDER BY tags.level, tags.tagid
      

      【讨论】:

      • 不同寻常的是,这与an answer I gave a few days ago 几乎相同的问题几乎相同。这是课程作业吗?
      • 不同寻常的是,您的查询无法完成这项工作。请参阅下面 Nawfal 的回答。
      • @OctavianVladu:OP 要求使用两个标签 16 32 标记的新闻项目的标签,而不是非此即彼(这是 nawfal 的答案所做的)。粗鲁多少?
      • 我认为你已经完成了假设AND。我觉得IN 代表OR。我对 OP 要求的理解可能是错误的
      • @nawfal: 查询的目的是查找已被标记为tagid 16 32的标签的“新闻”项目。也许我们应该等待 OP 澄清他的意图?
      猜你喜欢
      • 2017-08-24
      • 1970-01-01
      • 2014-03-15
      • 2015-06-04
      • 1970-01-01
      • 2011-08-04
      • 1970-01-01
      • 1970-01-01
      • 2017-07-12
      相关资源
      最近更新 更多