MySQL 使用临时排序;使用文件排序

标签 mysql indexing query-optimization query-performance

这是我要发起的查询:

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对于此查询输出:

enter image description here

这是 creatives表结构:

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需要临时表,为什么不能先where filter + sort by c.gravity , 然后按结果表分组并根据 HAVING 过滤条款。筛选后的表将按 c.gravity 排序正确,因为重力值在分组和过滤后保持不变。

我尝试过的:

  1. 选择了没有 ORDER BY 的所有内容, 包装到子查询中并在 creatives 上再次加入表 - 相同的结果,使用临时、文件排序和慢速

  2. 尝试添加 FORCE USE INDEX FOR ORDER BY (gravity)它不会改变任何东西。 EXPLAIN和执行时间保持不变。

更新:问题已由 @Rick 回答并且使用他的相关子查询并且不使用 GROUP BY 确实快得多.我在这里添加一个 EXPLAIN查询输出:

enter image description here

SHOW CREATE TABLE term_relationships的输出使用新创建的索引:

enter image description here

还有一个问题要问@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;

我的理解是否正确,或者我在您的查询中遗漏了什么?

最佳答案

临时表和文件排序本身并不是罪魁祸首。这就是它们的体积。

这可能看起来更复杂,但它可能更快:

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) 进行内部查询,那么它将在找到包含所有 5 个事务的 30 行后停止。如果它生成一个 tmp 表,它将只有 30 行——比您的原始查询要好得多。另请注意,tmp 表会更窄——其中只有 creative_id。最后,它返回到 creatives 以获取所需列的其余部分。最后,还有另一种排序,但只有 30 行。

此外,“文件排序”在 RAM 中通常是一种非常快速的排序,而不是真正的"file"排序。我很确定我的查询不会在磁盘上。

term_relationships 需要这个复合索引:INDEX(creative_id, term_id)

关于MySQL 使用临时排序;使用文件排序,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37117058/

相关文章:

php - mySQL 和 Php 网站之间的连接如何工作?

mysql - 需要帮助验证我的 MySQL 语法

mysql - 使用 like 通过查询实现数据库搜索

sql - 我如何强制 Postgres 使用特定的索引?

java - Solr 索引时搜索

mysql - SQL 位于 : better use a static array or a select? 中

mysql - 1329 无数据 - 提取、选择或处理的行为零

sql - Postgres 不使用索引,即使返回的行少于 5%

sql - 关于如何替换MariaDB语句中的UNION子句的思考

mysql - 如何使用多个 AND 和 OR 优化这个慢查询