MySQL使用临时排序;使用filesort

时间:2016-05-09 13:28:35

标签: 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 filesortGROUP BY启动ORDER BY时,我很担心ORDER BY。如果我删除c.gravity,临时文件和文件存档就会消失,查询运行得非常快。

我不明白,为什么mysql需要临时表,为什么不能首先在哪里过滤+按HAVING排序,然后按结果表分组并根据c.gravity子句进行过滤。过滤后的表格将按ORDER BY正确排序,因为在分组和过滤后重力值保持不变。

我尝试了什么:

  1. 选择没有creatives的所有内容,包装到子查询中并再次加入FORCE USE INDEX FOR ORDER BY (gravity)表 - 相同的结果,使用临时,filesort和慢

  2. 试图添加EXPLAIN并且它不会改变任何内容。 GROUP BY和执行时间保持不变。

  3. 更新:问题已由@Rick回答,并且使用相关子查询并且不使用EXPLAIN的速度确实快得多。我在这里为查询添加SHOW CREATE TABLE term_relationships输出:

    enter image description here

    c3的输出与新创建的索引:

    enter image description here

    还有一个问题要问@Rick:为什么我们需要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; 中添加缺少的列:

    <?php
    include('Crypt/AES.php');
    include('Crypt/Random.php');
    
    $cipher = new Crypt_AES(); // could use CRYPT_AES_MODE_CBC
    // keys are null-padded to the closest valid size
    // longer than the longest key and it's truncated
    //$cipher->setKeyLength(128);
    $cipher->setKey('abcdefghijklmnop');
    // the IV defaults to all-NULLs if not explicitly defined
    $cipher->setIV(crypt_random_string($cipher->getBlockLength() >> 3));
    
    $size = 10 * 1024;
    $plaintext = str_repeat('a', $size);
    
    echo $cipher->decrypt($cipher->encrypt($plaintext));
    ?>
    

    我的理解是正确的还是我在查询中遗漏了什么?

1 个答案:

答案 0 :(得分:2)

Temp table和filesort本身并不是恶棍。这是多么笨重。

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

#[payload.get(0).get('DELODNBLS')]

如果恰好使用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 作为内部查询,那么它将在找到包含所有5个事务的30行后停止。如果它生成一个tmp表,它将只有30行 - 比原始查询好得多。另请注意,tmp表将更窄 - 只有INDEX(gravity)。最后它回到creative_id以获得所需的其余列。最后,会有另一种,但只有30行。

此外,&#34; filesort&#34;在RAM中通常是一种非常快速的排序,而不是真正的&#34;文件&#34;分类。我非常确定我的查询不会在磁盘上。

creatives需要此综合索引:term_relationships