MySQL 使用临时排序;使用文件排序
MySQL sorting with Using temporary; Using filesort
这是我要启动的查询:
SELECT c.creative_id, c.creative_title, c.creative_image_name, c.gravity, c.ad_strength
FROM creatives AS c
INNER JOIN term_relationships AS tr ON c.creative_id = tr.creative_id
WHERE tr.term_id
IN ( 14, 1, 50, 76, 104 )
GROUP BY c.creative_id
HAVING COUNT(tr.term_id ) =5
ORDER BY c.gravity ASC
LIMIT 30;
以下是此查询的 EXPLAIN
输出:
这里是 creatives
table 结构:
CREATE TABLE `creatives` (
`creative_id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`scraper_id` bigint(20) unsigned DEFAULT NULL,
`creative_title` varchar(255) NOT NULL,
`creative_image_name` varchar(255) DEFAULT NULL,
`image_attrib` varchar(12) DEFAULT NULL,
`original_image_name` varchar(255) DEFAULT NULL,
`creative_subtext` varchar(255) DEFAULT NULL,
`dest_url` varchar(2083) NOT NULL,
`lp_url` varchar(2083) NOT NULL,
`lp_image_name` varchar(255) DEFAULT NULL,
`lp_image_flag` tinyint(1) unsigned NOT NULL DEFAULT '0',
`creative_first_seen` date NOT NULL,
`creative_last_seen` date NOT NULL,
`daily_ad_count` int(5) unsigned NOT NULL,
`ad_strength` int(11) unsigned NOT NULL,
`prev_ad_strength` int(11) unsigned DEFAULT NULL,
`gravity` int(11) unsigned DEFAULT NULL,
PRIMARY KEY (`creative_id`),
KEY `gravity` (`gravity`)
) ENGINE=InnoDB AUTO_INCREMENT=173037591 DEFAULT CHARSET=utf8
我担心 Using temporary; using filesort
在另一列上同时使用 GROUP BY
和 ORDER BY
启动时。如果我删除 ORDER BY
,临时文件和文件排序就会消失,查询运行得非常快。
我不明白的是,为什么 mysql 需要临时的 table,为什么它不能先过滤 + 按 c.gravity
排序,然后按结果分组 table 并根据 HAVING
子句进行过滤。过滤后的table会正确的按c.gravity
排序,因为重力值在分组过滤后保持不变。
我尝试了什么:
选择了没有 ORDER BY
的所有内容,包装到子查询中并在 creatives
上再次加入 table - 结果相同,使用临时文件排序和慢速
尝试添加 FORCE USE INDEX FOR ORDER BY (gravity)
但它没有任何改变。 EXPLAIN
和执行时间保持不变。
UPDATE:@Rick 已经回答了这个问题,使用他的相关子查询比不使用 GROUP BY
确实快得多。我在这里为查询添加一个 EXPLAIN
输出:
并且 SHOW CREATE TABLE term_relationships
的输出带有新创建的索引:
还有一个问题要问@Rick:为什么我们需要 c3
的外部查询?似乎只是将 creatives
单独加入一个,只是为了从其他列中获取值并按重力对记录进行排序。但是,它们已经使用内部查询进行了排序,我们可以轻松地在 c1
中添加缺失的列使其成为:
SELECT c1.creative_id,c1.creative_title,c1.creative_image_name,c1.gravity, c1.ad_strength
FROM creatives AS c1
WHERE
( SELECT COUNT(*)
FROM term_relationships
WHERE c1.creative_id = creative_id
AND term_id IN ( 14, 1, 50, 76, 104 )
) = 5
ORDER BY c1.gravity ASC
LIMIT 30;
我的理解是否正确,或者我在您的查询中遗漏了什么?
Temp table 和 filesort 本身并不是坏人。这就是它们的体积。
这可能看起来更复杂,但速度可能更快:
SELECT c3.creative_id,
c3.creative_title, c3.creative_image_name,
c3.gravity, c3.ad_strength
FROM
( SELECT creative_id
FROM creatives AS c1
WHERE
( SELECT COUNT(*)
FROM term_relationships
WHERE c1.creative_id = creative_id
AND term_id IN ( 14, 1, 50, 76, 104 )
) = 5
ORDER BY c1.gravity ASC
LIMIT 30
) AS c2
JOIN creatives c3 USING (creative_id)
ORDER BY c3.gravity
如果恰好使用INDEX(gravity)
进行内部查询,那么它会在找到30行包含所有5个事务后停止。如果它生成一个 tmp table,它将只有 30 行——比您的原始查询好得多。另请注意,tmp table 会更窄——只有 creative_id
会在其中。最后它返回到 creatives
以获取所需列的其余部分。最后还有一个排序,但是只有30行。
此外,"filesort" 在 RAM 中通常是一种非常快速的排序,而不是真正的 "file" 排序。我很确定我的查询不会在磁盘上。
term_relationships
需要这个复合索引:INDEX(creative_id, term_id)
.
这是我要启动的查询:
SELECT c.creative_id, c.creative_title, c.creative_image_name, c.gravity, c.ad_strength
FROM creatives AS c
INNER JOIN term_relationships AS tr ON c.creative_id = tr.creative_id
WHERE tr.term_id
IN ( 14, 1, 50, 76, 104 )
GROUP BY c.creative_id
HAVING COUNT(tr.term_id ) =5
ORDER BY c.gravity ASC
LIMIT 30;
以下是此查询的 EXPLAIN
输出:
这里是 creatives
table 结构:
CREATE TABLE `creatives` (
`creative_id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`scraper_id` bigint(20) unsigned DEFAULT NULL,
`creative_title` varchar(255) NOT NULL,
`creative_image_name` varchar(255) DEFAULT NULL,
`image_attrib` varchar(12) DEFAULT NULL,
`original_image_name` varchar(255) DEFAULT NULL,
`creative_subtext` varchar(255) DEFAULT NULL,
`dest_url` varchar(2083) NOT NULL,
`lp_url` varchar(2083) NOT NULL,
`lp_image_name` varchar(255) DEFAULT NULL,
`lp_image_flag` tinyint(1) unsigned NOT NULL DEFAULT '0',
`creative_first_seen` date NOT NULL,
`creative_last_seen` date NOT NULL,
`daily_ad_count` int(5) unsigned NOT NULL,
`ad_strength` int(11) unsigned NOT NULL,
`prev_ad_strength` int(11) unsigned DEFAULT NULL,
`gravity` int(11) unsigned DEFAULT NULL,
PRIMARY KEY (`creative_id`),
KEY `gravity` (`gravity`)
) ENGINE=InnoDB AUTO_INCREMENT=173037591 DEFAULT CHARSET=utf8
我担心 Using temporary; using filesort
在另一列上同时使用 GROUP BY
和 ORDER BY
启动时。如果我删除 ORDER BY
,临时文件和文件排序就会消失,查询运行得非常快。
我不明白的是,为什么 mysql 需要临时的 table,为什么它不能先过滤 + 按 c.gravity
排序,然后按结果分组 table 并根据 HAVING
子句进行过滤。过滤后的table会正确的按c.gravity
排序,因为重力值在分组过滤后保持不变。
我尝试了什么:
选择了没有
ORDER BY
的所有内容,包装到子查询中并在creatives
上再次加入 table - 结果相同,使用临时文件排序和慢速尝试添加
FORCE USE INDEX FOR ORDER BY (gravity)
但它没有任何改变。EXPLAIN
和执行时间保持不变。
UPDATE:@Rick 已经回答了这个问题,使用他的相关子查询比不使用 GROUP BY
确实快得多。我在这里为查询添加一个 EXPLAIN
输出:
并且 SHOW CREATE TABLE term_relationships
的输出带有新创建的索引:
还有一个问题要问@Rick:为什么我们需要 c3
的外部查询?似乎只是将 creatives
单独加入一个,只是为了从其他列中获取值并按重力对记录进行排序。但是,它们已经使用内部查询进行了排序,我们可以轻松地在 c1
中添加缺失的列使其成为:
SELECT c1.creative_id,c1.creative_title,c1.creative_image_name,c1.gravity, c1.ad_strength
FROM creatives AS c1
WHERE
( SELECT COUNT(*)
FROM term_relationships
WHERE c1.creative_id = creative_id
AND term_id IN ( 14, 1, 50, 76, 104 )
) = 5
ORDER BY c1.gravity ASC
LIMIT 30;
我的理解是否正确,或者我在您的查询中遗漏了什么?
Temp table 和 filesort 本身并不是坏人。这就是它们的体积。
这可能看起来更复杂,但速度可能更快:
SELECT c3.creative_id,
c3.creative_title, c3.creative_image_name,
c3.gravity, c3.ad_strength
FROM
( SELECT creative_id
FROM creatives AS c1
WHERE
( SELECT COUNT(*)
FROM term_relationships
WHERE c1.creative_id = creative_id
AND term_id IN ( 14, 1, 50, 76, 104 )
) = 5
ORDER BY c1.gravity ASC
LIMIT 30
) AS c2
JOIN creatives c3 USING (creative_id)
ORDER BY c3.gravity
如果恰好使用INDEX(gravity)
进行内部查询,那么它会在找到30行包含所有5个事务后停止。如果它生成一个 tmp table,它将只有 30 行——比您的原始查询好得多。另请注意,tmp table 会更窄——只有 creative_id
会在其中。最后它返回到 creatives
以获取所需列的其余部分。最后还有一个排序,但是只有30行。
此外,"filesort" 在 RAM 中通常是一种非常快速的排序,而不是真正的 "file" 排序。我很确定我的查询不会在磁盘上。
term_relationships
需要这个复合索引:INDEX(creative_id, term_id)
.