【问题标题】:Mysql Query is slow with one where condition on ExistsMysql 查询很慢,其中一个条件存在
【发布时间】:2022-01-19 02:40:06
【问题描述】:

我有 3 个表,它们之间的关系是多对多的。

This is the Image of my tables with its columns

我需要获取包含具有特定 category_id 文件的主题标签名称。

问题是,当我使用下面的查询而不指定类别时,它的性能像 0.05s 一样好。

select `hashtags`.`slug` 
from `hashtags` 
where EXISTS (
   select * from `files` 
   inner join `file_hashtags` on `files`.`id` = `file_hashtags`.`file_id` 
   where `hashtags`.`id` = `file_hashtags`.`hashtag_id` 
);

但是当我使用指定的类别执行以下查询时,它会像 3s 一样执行。

select `hashtags`.`slug` 
from `hashtags` 
where EXISTS (
   select * from `files` 
   inner join `file_hashtags` on `files`.`id` = `file_hashtags`.`file_id` 
   where `hashtags`.`id` = `file_hashtags`.`hashtag_id` 
   and `files`.`category_id`=2 
);

我可以做些什么来改进它以获得更好的查询时间?我也使用 IN 而不是 Exists 进行了此查询,但结果是相同的,在时间上表现得更好 0.1s。

关于索引:

  • files 表有 ID 作为主键,category_id 作为 BTREE 索引(当我需要执行简单的查询,比如获取具有特定类别的文件时需要这个),并且 slug 作为唯一索引。

  • hashtags 表有 ID 作为主键,slug 作为唯一索引。

  • file_hashtags 表有两个表的外键, (file_id , hashtag_id) 也是主键。

files 表中大约有 150k 行,hashtags 表中有 75 行,pivot 表中有 260k 行。

更新:

如果我在查询中使用 category=10,它的执行速度非常快,因为我得到了该类别的 3k 数据,但如果我使用 category=2,它会像 3 秒一样慢,因为我获得了超过 90k 的该类别数据,我希望这有助于找到问题。

另一件事是,当我使用解释时,它在第一个查询中使用文件表的主键,但在第二个查询中它使用文件表的类别索引,我认为这是正常行为。

【问题讨论】:

  • “这是我的表格及其列的图像” 不够全面。最好将SHOW CREATE TABLE table_name; 结果的结果发布到您的问题中。这也会显示所有的键和索引。
  • 另外,您可以通过在两个查询中添加EXPLAIN 来检查您的查询执行计划。
  • 我使用了解释,当 where 子句中没有类别时,它对文件表使用 PK,但是当 where 子句中有类别时,它使用我认为的正常行为

标签: mysql sql


【解决方案1】:

你可以在不存在的情况下使用内连接:

select distinct `hashtags`.`slug` 
from `hashtags`
inner join `file_hashtags` on `hashtags`.`id` = `file_hashtags`.`hashtag_id`
inner join `files` on `files`.`id` = `file_hashtags`.`file_id`
where `files`.`category_id`=2
group by `hashtags`.`slug`;

【讨论】:

  • 我用这个,我用这个得到4s查询时间。
  • 我认为自己的查询在我的帖子中没有错,但可能是数据类型、索引问题或其他问题。
  • 您确定 category_id 已编入索引吗?
  • 是的,它是索引 BTREE,我可以使用 navicat 看到。我有大约 10 个类别。
  • hashtag_idfile_id 也被编入索引了!!
【解决方案2】:

像这样试试。

SELECT hashtags.slug
FROM hashtags
WHERE EXISTS
(SELECT * FROM (SELECT * 
FROM files
WHERE category_id = 2) A
INNER JOIN file_hashtags ON A.id = file_hashtags.file_id
WHERE hashtags.id = file_hashtags.hashtag_id)

如果files表中的行数很大,可以通过在进行连接操作之前执行过滤器来减少连接记录的数量。

【讨论】:

    【解决方案3】:

    请使用SHOW CREATE TABLE。它显示你有什么索引

    我假设file_hashtags 是多对多映射?那么它需要

    PRIMARY KEY(file_id, hashtag_id)
    INDEX(hashtag_id, file_id)
    

    更多讨论:http://mysql.rjweb.org/doc.php/index_cookbook_mysql#many_to_many_mapping_table

    因为and files.category_id=2files需要

    INDEX(category_id)
    

    所以,有了这些 ID,只需查询:

    SELECT  h.slug
        FROM  files AS f
        JOIN  file_hashtags AS fh  ON fh.file_id = f.id
        JOIN  hashtags AS h  ON h.id = fh.hashtag_id
        WHERE  f.category_id = 2;
    

    (我假设id 是每个表的PRIMARY KEY,尽管idfile_hashtags不需要。)

    我不相信EXISTS 有助于提高此查询的性能。

    如果你没有使用ENGINE=InnoDB,我的回答是不够的。你应该使用 InnoDB。

    【讨论】:

    • 我认为不可能有任何 dup slug,所以我没有包括 DISTINCT。 (DISTINCTGROUP BY 会通过重复数据删除减慢查询速度,可能使用临时表和排序。)
    • 索引如下你写的,例如,如果我改变查询,使用不同的类别,它执行得很快,因为它的数据少,但是当类别=2时它很慢,因为我有更多该类别的数据超过 90k
    • 每张表大概有多少行?
    • 我在帖子中写道。 files 表中有大约 150k 行,hashtags 表中有 75 行,pivot 表中有 260k 行。
    • 您需要 90K 行的输出?这需要时间通过网络铲到客户端。如果查询还有更多内容,请出示。如果有更多列是SELECTedWHERE 中有更多条件,或者有LIMITORDER BY,这确实
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-03-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-06-24
    相关资源
    最近更新 更多