MySQL INTERSECT通过连接表?

Ste*_* C. 5 mysql has-and-belongs-to-many intersect

所以基本上我有两个表,包含URL和TAGS,通过连接表TAGS_URLS在两者之间具有has-and-belongs-to-many关系.

通过标记查找URL的简单查询将是:

SELECT urls.id FROM urls 
  INNER JOIN tags_urls ON urls.id=tags_urls.url_id
  INNER JOIN tags ON tags_urls.tag_id=tags.id 
WHERE tags.tag IN ("sample","tag","list");
Run Code Online (Sandbox Code Playgroud)

但是,我正在尝试恢复包含所有一组标记的所有URL的交集.即,只有包含标签"sample"和"tag"AND"list"的URL.

我有一个工作查询,但我无法在不到30秒的时间内执行查询.

SELECT a.id
  FROM
    (SELECT DISTINCT urls.id FROM urls
      INNER JOIN tags_urls ON tags_urls.url_id=urls.id INNER JOIN tags ON tags.id=tags_urls.tag_id
      WHERE tags.tag = 'sample') a
  JOIN
     (SELECT DISTINCT urls.id FROM urls
      INNER JOIN tags_urls ON tags_urls.url_id=urls.id INNER JOIN tags ON tags.id=tags_urls.tag_id
      WHERE tags.tag = 'list') b
  ON a.id = b.id;
Run Code Online (Sandbox Code Playgroud)

结果集是正确的,但性能可怕.

我目前还在Redis数据库中将数据复制为存储在标记集中的URL ID列表,这样我就可以做到这样的事情并很快得到结果集.

SINTER "tag-sample" "tag-list"
Run Code Online (Sandbox Code Playgroud)

通过合理的努力,是否有可能通过SINTER将此任务的MySQL性能提升到Redis级别?

Bra*_*rad 1

我不是 100% 确定,但我认为底层引擎正在为每个子选择创建一个临时表。根据数据的大小,这可能会相当昂贵。如果它们很大(在您的情况下),临时表必须将其内容写入磁盘,因为它们太大而无法立即保存在内存中。因此,基本上,您的查询正在复制大量数据,因为它试图构建两个与两个子选择的选择条件相匹配的临时表。一旦完成,它最终会执行外部选择,并且这很可能相当快。

我会尝试将子选择分解为内部联接。我认为以下内容将为您提供您正在寻找的内容:

select urls.id from urls
inner join tags_urls tu1 on tu1.url_id = urls.id
inner join tags t1 on tu1.tag_id = t1.id and t1.tag = 'sample'
inner join tag_urls tu2 on tu2.url_id = urls.id
inner join tags t2 on t2.id = tu2.tag_id and t2.tag = 'list'
Run Code Online (Sandbox Code Playgroud)

您将继续向 tag_urls 添加成对的内部联接,并为您想要与之相交的每个“标签”添加标签。再次,通过解释来运行它并确保所有内容都有正确的索引。

DBMS 可以很好地处理多个内部联接,但随着交集数量的增加,性能将会下降。