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, 9
和type 1
和标签81, 48, 56
和type 2
。我想返回所有帖子,其中包含提到的类型 1 和类型 2 的所有标签。如果有人可以向我展示一个带有 NOT IN 的查询示例,那就太好了,因为我也需要它。所以这将是错误的标签。
此时,该查询没有为我返回任何结果。仅当我从查询中删除 AND (
tag_idIN ( 81, 48, 56 ) AND
type = 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 个加上第二组的一些帖子
我们可以通过JOIN
或LEFT 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_id
和type
获取帖子。
请不要叫tags_table_one
;离开“_one”让我很困惑。
是否有多个类型的 tag_id=15 条目?我真的需要了解为什么type
存在。
好的,你可以删除“_one”了。表的名称保持不变,但我现在只使用一张表。没有更多的桌子了。以上是关于MYSQL查询性能与优化的主要内容,如果未能解决你的问题,请参考以下文章