MYSQL查询性能与优化

Posted

技术标签:

【中文标题】MYSQL查询性能与优化【英文标题】:MYSQL Query Performance and Optimization 【发布时间】:2021-09-10 15:10:24 【问题描述】:

我有以下数据库结构:

CREATE TABLE `posts` (
 `id` int(11) NOT NULL AUTO_INCREMENT,
 `title` varchar(255) NOT NULL,
 PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

CREATE TABLE `tags` (
 `id` int(11) NOT NULL AUTO_INCREMENT,
 `name` varchar(255) NOT NULL,
 `seo` varchar(255) NOT NULL,
 PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

CREATE TABLE `tags_table_one` (
 `id` int(11) NOT NULL AUTO_INCREMENT,
 `post_id` int(11) NOT NULL,
 `tag_id` int(11) NOT NULL,
 PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

CREATE TABLE `tags_table_three` (
 `id` int(11) NOT NULL AUTO_INCREMENT,
 `post_id` int(11) NOT NULL,
 `tag_id` int(11) NOT NULL,
 PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

CREATE TABLE `tags_table_two` (
 `id` int(11) NOT NULL AUTO_INCREMENT,
 `post_id` int(11) NOT NULL,
 `tag_id` int(11) NOT NULL,
 PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

将数据添加到此表后:

帖子(~ 500k 行) 标签(~ 30k 行) tags_table_one(~500万行) tags_table_two(~ 700k 行) tags_table_three(~ 800k 行)

这是我尝试过的查询,但它很慢:

SELECT p.title
FROM   `tags_table_one` x
       JOIN `posts` p
         ON x.post_id = p.id
WHERE  `tag_id` IN ( 244, 229, 193, 93 )
GROUP  BY `post_id`
HAVING Count(*) = 4
       AND NOT EXISTS (SELECT 1
                       FROM   `tags_table_one`
                       WHERE  `post_id` = x.post_id
                              AND `tag_id` IN ( 92, 10, 234 )) 

我的目标是按标签(好标签和坏标签)进行搜索过滤。例如,假设我有 good tags "244","229","193","93" 和 bad tags "92","10","234"。我需要一个 MYSQL 查询来按指定标签过滤这些帖子。结果应该返回所有具有提及的所有好标签未提及所有坏标签的帖子(因此它应该包含所有而不只是一些)。问题是,可以从任何 tags_table 中选择好的和坏的标签,所以我认为它需要一个 JOIN 或类似的东西,我没有添加到我的查询中,因为我不知道该怎么做。我之前尝试过,结果很糟糕,可能是因为表中有很多记录(在某些情况下,查询花费了 30-40 秒,执行时间太长)。有一些记录可以快速工作,但我不需要。我需要一个优化的数据库/查询以使其尽可能快。如果您有任何数据库/查询示例,将是很好的尝试。即使 mysql 配置有任何更改或我很高兴听到的内容。

编辑:

查询的解释:

编辑 2:

我将所有的数据都移到了一个带有类型列的表中,现在我的表结构是这样的:

CREATE TABLE `tags_table_one` (
 `post_id` mediumint(8) unsigned NOT NULL,
 `tag_id` mediumint(8) unsigned NOT NULL,
 `type` tinyint(1) NOT NULL,
 PRIMARY KEY (`post_id`,`tag_id`,`type`),
 KEY `tag_id` (`tag_id`,`post_id`,`type`),
 KEY `type` (`type`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

根据@RickJames 解决方案,但现在我有以下查询:

SELECT posts.id AS id,
       posts.title
FROM   `tags_table_one`
       INNER JOIN posts
               ON tags_table_one.post_id = posts.id
WHERE  ( `tag_id` IN ( 15, 25, 16, 17,
                       234, 14, 9 )
         AND `type` = 1 )
       AND ( `tag_id` IN ( 81, 48, 56 )
             AND `type` = 2 )
       AND posts.active = '1'
GROUP  BY `post_id`
HAVING Count(*) = "7" 

假设我有标签15, 25, 16, 17, 234, 14, 9type 1 和标签81, 48, 56type 2。我想返回所有帖子,其中包含提到的类型 1 和类型 2 的所有标签。如果有人可以向我展示一个带有 NOT IN 的查询示例,那就太好了,因为我也需要它。所以这将是错误的标签。

此时,该查询没有为我返回任何结果。仅当我从查询中删除 AND ( tag_idIN ( 81, 48, 56 ) ANDtype = 2 ) 部分但不是我想要的,因为没有正确过滤。

编辑 3

我试图管理它,但我只有这个不起作用的查询:

SELECT p.id,
       p.title
FROM `posts` p
INNER JOIN tags_table_one t1 ON p.id=t1.post_id
INNER JOIN tags_table_one t2 ON p.id=t2.post_id
INNER JOIN tags_table_one t3 ON p.id=t3.post_id
WHERE p.active='1'
  AND t1.tag_id IN (15, 25, 16, 17, 234, 14, 9) AND t1.type = '1'
  AND t2.tag_id IN (81, 48, 56) AND t2.type = '2'
  AND t3.tag_id IN (47, 51, 355, 71) AND t3.type = '3'
GROUP BY p.id 
HAVING COUNT(t1.tag_id) = 7
AND
HAVING COUNT(t2.tag_id) = 3
AND
HAVING COUNT(t3.tag_id) = 4
ORDER BY p.id DESC

问题出在“HAVING COUNT”上,如果我删除它可以工作但不能过滤。

【问题讨论】:

很抱歉,我不清楚您要求我们为您提供什么帮助!如果您在加速现有查询方面需要帮助,那么我们需要知道查询是什么、一些示例数据、预期结果、详细的表定义(包括所有索引)以及您查询的解释输出。如果您在数据库设计方面需要帮助,那么您需要了解我们可以在通用概念方面提供帮助,例如如何建模多对多关系。我们无法真正帮助您为您设计数据库,因为它需要深入了解您的数据、业务需求和业务 三个不同的标签表似乎是个错误。 @ysth 这不是一个错误,因为每个标签表都有不同的数据,我不想在一个表中。 @Shadow 我用我尝试的一些详细的数据库结构和查询编辑了我的帖子。 post id 和 tag id 字段没有被索引,这意味着您的查询必须使用全表扫描。我还将三个标签表组合在一起,并使用第 4 个字段来区分这 3 种类型 - 但这是不了解您的业务或您的业务需求的地方 【参考方案1】:
CREATE TABLE `tags_table_one` (
 `post_id` int(11) NOT NULL,
 `tag_id` int(11) NOT NULL,
 PRIMARY KEY (post_id, tag_id),
 INDEX(tag_id, post_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

注意事项:

摆脱id。 使复合索引双向;让其中之一成为 PK。

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

INDEX(post_id), INDEX(tag_id) 会更快,但仍然(如您所见)相当慢。

tags 可能需要INDEX(name)

修改后

SELECT  posts.id AS id, posts.title
    FROM  `tags_table_one`
    INNER JOIN  posts  ON tags_table_one.post_id = posts.id
    WHERE  `tag_id` IN ( 15, 25, 16, 17, 234, 14, 9 )
      AND  `type` = 1 
      AND  posts.active = '1'
    GROUP BY  `post_id`
    HAVING  Count(*) = "7" 

SELECT  posts.id AS id, posts.title
    FROM  `tags_table_one`
    INNER JOIN  posts  ON tags_table_one.post_id = posts.id
    WHERE  `tag_id` IN ( 81, 48, 56 )
      AND  `type` = 2 
      AND  posts.active = '1'
    GROUP BY  `post_id`
    HAVING  Count(*) = "3" 

这些选择中的第一个给出了包含所有 7 个 tag_id 的帖子。 这些 Selects 中的第二个给出了包含第二组 tag_ids 中的所有 3 个的帖子。

你想要什么?

A.包含所有 10 个标签的帖子 B. 第一组全部 7 个,第二组都没有的帖子 C. 第一组全部 7 个加上第二组的一些帖子

我们可以通过JOINLEFT JOIN...IS NOT NULL 来获取 (A) 或 (B)。 (C) 将需要更多的摆弄。

type 是从哪里来的?既然你在这两种情况下都在阅读tags_table_one,我猜这不是“一/二/三”??

更多

当您说 AND t2.tag_id IN (19, 684) AND t2.type = 2 时,您允许它具有 任一 19 或 684。但听起来您希望它具有 两者 19 和 684。那将需要不同的 SQL。

方案 A:将一个 JOIN + IN 变成两个 JOINs

B 计划:继续使用JOIN + IN,但使用HAVING count(*) = 2。但这会变得很混乱,因为您在同一个查询中有多个这样的子句。

C 计划:使用GROUP_CONCAT(tag_id ORDER BY tag_id) = "19,684"(数字按数字顺序排列)。这还有其他复杂性。

计划 D:如果您总是需要所有给定的标签,那么

WHERE t1.tag_id IN (15, 223) AND t1.type = 1
  AND t2.tag_id IN (19, 684) AND t2.type = 2
  AND t3.tag_id IN (5) AND t3.type = 4

HAVING COUNT(*) = 5

可能会工作。

我认为 D 计划最有希望;试一试。

【讨论】:

我编辑了我的问题,我有一些查询问题。 type 来自tags_table_one 当我将所有数据移到此表中时,我使用 1,2,3 过滤(而不是使用多个表:tags_table_one = type 1, tags_table_two = type 2、tags_table_three = 类型 3)。我认为 (A) 和 (B) 这是我想要的,但 type 需要属于每组标签 ( 15, 25, 16, 17, 234, 14, 9 ) AND type = 1 / ( 81, 48, 56 ) AND type = 2 。我需要根据特定的tag_idtype 获取帖子。 请不要叫tags_table_one;离开“_one”让我很困惑。 是否有多个类型的 tag_id=15 条目?我真的需要了解为什么type 存在。 好的,你可以删除“_one”了。表的名称保持不变,但我现在只使用一张表。没有更多的桌子了。

以上是关于MYSQL查询性能与优化的主要内容,如果未能解决你的问题,请参考以下文章

MYSQL查询性能与优化

mysql问题排查与性能优化

MySQL性能优化

优化解决 MySQL 查询速度慢与性能差

mysql性能优化-慢查询分析优化索引和配置

mysql性能优化-慢查询分析优化索引和配置