Table ideas: id, author_id, some_columns
Table ideas_tags: idea_id, tag_name
Table ideas_seen: idea_id, user_id
Table user: uid, ban, some_columns
我需要从列表中获取 10 个具有标签的想法,其作者未被禁止,并且不在当前用户的 ideas_seen 中。
现在我的查询看起来像这样:
SELECT
ideas.*, GROUP_CONCAT(DISTINCT IT_V.tag_name SEPARATOR '|||') AS tags
FROM `ideas`
LEFT JOIN ideas_tags IT
ON ideas.id=IT.idea_id
LEFT JOIN ideas_tags IT_V
ON ideas.id=IT_V.idea_id
LEFT JOIN ideas_seen IV
ON ideas.id=IV.idea_id AND IV.user_id=145974517
LEFT JOIN users ON users.uid=ideas.author_id
WHERE author_id!=145974517 AND IV.id IS NULL AND ( (IT.tag_name = 'some_tag') OR (IT.tag_name = 'another_tag') OR (IT.tag_name IS NULL) ) AND active=1 AND deleted=0 AND (users.ban=0 OR users.ban IS NULL)
GROUP BY ideas.id
ORDER BY id DESC
LIMIT 10
这是网站上最慢的查询,我不知道如何加快它的速度。
CREATE TABLE IF NOT EXISTS `ideas` (
`id` int(11) NOT NULL,
`tutorial` tinyint(4) NOT NULL,
`text` text NOT NULL,
`author_id` int(11) NOT NULL,
`active` bit(1) NOT NULL,
`timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`views` int(11) NOT NULL,
`views_all` int(11) DEFAULT '0',
`deleted` tinyint(4) NOT NULL DEFAULT '0',
`many_users` tinyint(4) DEFAULT NULL,
`game_id` int(11) DEFAULT NULL
) ENGINE=InnoDB AUTO_INCREMENT=35983 DEFAULT CHARSET=utf8;
CREATE TABLE IF NOT EXISTS `ideas_seen` (
`id` int(11) NOT NULL,
`idea_id` int(11) NOT NULL,
`user_id` int(11) NOT NULL
) ENGINE=InnoDB AUTO_INCREMENT=3694368 DEFAULT CHARSET=utf8;
CREATE TABLE IF NOT EXISTS `ideas_tags` (
`id` int(11) NOT NULL,
`idea_id` int(11) NOT NULL,
`tag_name` tinytext NOT NULL
) ENGINE=InnoDB AUTO_INCREMENT=86832 DEFAULT CHARSET=utf8;
CREATE TABLE IF NOT EXISTS `users` (
`uid` int(11) NOT NULL,
`email` tinytext,
`password_hash` tinytext,
`restore_code` tinytext NOT NULL,
`last_action` timestamp NULL DEFAULT NULL,
`score` float NOT NULL,
`date_register` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`posts_length` int(11) DEFAULT NULL,
`settings` text,
`titles` int(11) NOT NULL DEFAULT '1',
`filter` int(11) NOT NULL DEFAULT '1',
`note` text NOT NULL,
`ban` tinyint(4) DEFAULT NULL,
`mod_send` smallint(6) DEFAULT '0',
`mod_get` int(11) DEFAULT '0',
`fp_notified` int(11) NOT NULL DEFAULT '0',
`skilled` tinyint(4) NOT NULL DEFAULT '0',
`show_only_skilled` tinyint(4) NOT NULL DEFAULT '0'
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
ALTER TABLE `ideas`
ADD PRIMARY KEY (`id`), ADD KEY `author_id` (`author_id`), ADD KEY `game_id` (`game_id`), ADD KEY `active` (`active`), ADD KEY `many_users` (`many_users`), ADD KEY `deleted` (`deleted`), ADD KEY `tutorial` (`tutorial`), ADD FULLTEXT KEY `idea_text` (`text`);
ALTER TABLE `ideas_seen`
ADD PRIMARY KEY (`id`), ADD KEY `user_id` (`user_id`), ADD KEY `idea_id` (`idea_id`);
ALTER TABLE `ideas_tags`
ADD PRIMARY KEY (`id`), ADD KEY `tag_name` (`tag_name`(255)), ADD KEY `idea_id` (`idea_id`);
ALTER TABLE `users`
ADD PRIMARY KEY (`uid`), ADD UNIQUE KEY `email` (`email`(255)), ADD KEY `ban` (`ban`), ADD KEY `fp_notified` (`fp_notified`), ADD KEY `skilled` (`skilled`);
ALTER TABLE `ideas`
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT,AUTO_INCREMENT=35983;
ALTER TABLE `ideas_seen`
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT,AUTO_INCREMENT=3694368;
ALTER TABLE `ideas_tags`
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT,AUTO_INCREMENT=86832;
ALTER TABLE `ideas`
ADD CONSTRAINT `ideas_ibfk_1` FOREIGN KEY (`game_id`) REFERENCES `games` (`id`) ON DELETE NO ACTION;
ALTER TABLE `ideas_seen`
ADD CONSTRAINT `ideas_seen_ibfk_1` FOREIGN KEY (`idea_id`) REFERENCES `ideas` (`id`) ON DELETE CASCADE ON UPDATE NO ACTION;
ALTER TABLE `ideas_tags`
ADD CONSTRAINT `ideas_tags_ibfk_2` FOREIGN KEY (`idea_id`) REFERENCES `ideas` (`id`) ON DELETE CASCADE ON UPDATE NO ACTION;
最佳答案
不要使用
TINYTEXT
;更改为VARCHAR(255)
并摆脱索引的“前缀”。即更改INDEX email(255)
至INDEX(email)
.不要索引“标志”,这样的索引不会被使用,因为它们没有用。示例:
deleted
不要让一个索引成为另一个索引的“左侧”部分。示例
PRIMARY KEY(id)
对比INDEX(id, ...)
.在PRIMARY KEY
的情况下, 收下;将另一个扔掉,因为它没有提供额外的好处。我认为不需要加入
idea_tags
两次;看看你是否可以避免这种情况。查询患有“膨胀-收缩”综合症。首先它使用
JOINs
增加行数,然后使用GROUP BY
回到原来的行(少了一些被过滤掉的行。)这样做,笨重的ideas.*
在临时表中移动。TEXT
(包括TINYTEXT
)阻止更有效地使用MEMORY
用于 tmp 表。
让我们来看看 inflate-deflate 的消除。
首先,让我们构建外部部分:
SELECT ideas.*, ( ??? ) as tags
FROM ideas
WHERE ???
ORDER BY ideas.id DESC
LIMIT 10;
假设我们可以填写???
,我们现在有了更快评估的途径。这需要在 id
上建立索引,你有 PRIMARY KEY(id)
.幸运的话(如果没有 WHERE
),只有 10 行需要被触及。 (在您的版本中,必须对整个表进行收集、分组、排序,然后才交付 10 个。)
因为你所有的JOINs
是 LEFT JOINs
, 我们可以证明 WHERE
涉及 ideas
以外表格的子句不会过滤掉任何行。留下
WHERE author_id!=145974517
AND active=1
AND deleted=0
为此,让我们有(虽然我不确定它会被使用):
INDEX(active, deleted, author_id)
返回AS tags
... 现在剥离查询以获取 tag_name
GROUP_CONCAT
的值(value)对于给定的 ideas.id
:
SELECT GROUP_CONCAT(DISTINCT IT_V.tag_name SEPARATOR '|||') AS tags
FROM ideas_tags IT_V ON ideas.id = IT_V.idea_id
AND ( IT.tag_name = 'some_tag'
OR IT.tag_name = 'another_tag'
OR IT.tag_name IS NULL
)
(这就是我迷失了为什么有两个连接到 idea_tags
的地方。)同时,我建议 SELECT
可以是获取tags
的子查询.
嗯..怎么样
LEFT JOIN users ON users.uid = ideas.author_id
WHERE ( users.ban=0 OR users.ban IS NULL )
这似乎没有过滤,因为它是LEFT
.它似乎提供没有列,因为users
其他地方没有提到。所以,我必须假设这是浪费的代码?
同上
LEFT JOIN ideas_seen IV ON ideas.id = IV.idea_id
AND IV.user_id=145974517
WHERE IV.id IS NULL
所以,它归结为删除一些索引,添加一个索引,并将查询重写为
SELECT ideas.*,
( SELECT GROUP_CONCAT(DISTINCT IT_V.tag_name SEPARATOR '|||')
FROM ideas_tags IT_V
WHERE ideas.id = IT_V.idea_id
AND ( IT.tag_name = 'some_tag'
OR IT.tag_name = 'another_tag'
OR IT.tag_name IS NULL )
) as tags
FROM ideas
WHERE author_id!=145974517
AND active=1
AND deleted=0
ORDER BY ideas.id DESC
LIMIT 10;
可能是 DISTINCT
是不必要的。
关于mysql - 如何加快慢速 SQL 查询,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44134499/