MySQL ORDER BY使用filesort(2个连接表)

时间:2016-07-26 08:45:45

标签: mysql sql database query-optimization

我在查询优化方面遇到了奇怪的问题。 SQL是由类似ORM的库生成的,只有在读取了兆字节的SQL日志后才会检测到错误。

SELECT  
  `ct_pricelistentry`.`uid` as `uid`, `ct_pricelistentry`.`skuGroup` as `skuGroup`
FROM 
  `ct_pricelistentry` INNER JOIN `lct_set` 
ON `lct_set`.`parent_uid`='SET:ALLPRICELISTENTRIES' AND 
   `lct_set`.`ref_uid`=`ct_pricelistentry`.`uid` 
WHERE 
  (`isGroup` IS FALSE) AND 
  (`isService` IS FALSE) AND 
  (`brand` = 'BRAND:5513f43697d637.00632331' OR `brand` IS NULL) 
ORDER BY `skuGroup` ASC

EXPLAIN说:

  

'1','SIMPLE','ct_pricelistentry','ALL','PRIMARY',NULL,NULL,NULL,'34591','使用where;使用filesort '

     

'1','SIMPLE','lct_set','eq_ref','PRIMARY','PRIMARY','292','const,dealers_v2.ct_pricelistentry.uid','1','使用位置;使用索引'

注意:显示所有需要的索引,包括skuGroup。但索引skuGroup仍未在EXPLAIN possible_keys中列出。它也不能被FORCE INDEX强制(它只是禁用所有索引)。

经过一番研究后,我发现了hacky解决方案,但不确定它是否有效:

  1. 添加FORCE INDEX (skuGroup)
  2. 添加到WHERE子句虚拟AND (skuGroup IS NULL OR skuGroup IS NOT NULL)部分。
  3. 以下查询

    SELECT  
      `ct_pricelistentry`.`uid` as `uid`, `ct_pricelistentry`.`skuGroup` as `skuGroup`
    FROM 
      `ct_pricelistentry` FORCE INDEX (`skuGroup`) INNER JOIN `lct_set` 
    ON `lct_set`.`parent_uid`='SET:ALLPRICELISTENTRIES' AND
       `lct_set`.`ref_uid`=`ct_pricelistentry`.`uid` 
    WHERE 
      (`isGroup` IS FALSE) AND 
      (`isService` IS FALSE) AND 
      (`brand` = 'BRAND:5513f43697d637.00632331' OR `brand` IS NULL) AND
      (`skuGroup` IS NULL OR `skuGroup` IS NOT NULL)
    ORDER BY `skuGroup` ASC
    

    给EXPLAIN没有filesort所以它似乎使用索引来获取有序行:

      

    '1','SIMPLE','ct_pricelistentry','range','skuGroup','skuGroup','768',NULL,'16911','使用位置'

         

    '1','SIMPLE','lct_set','eq_ref','PRIMARY','PRIMARY','292','const,dealers_v2.ct_pricelistentry.uid','1','使用位置;运用    索引'

    发生了什么事? 这是MySQL的错误吗?我已经在MySQL 5.1 - 5.5上测试了相同的结果。您有更多可预测/稳定的解决方案吗?

    ---- CREATE TABLE ----
    CREATE TABLE IF NOT EXISTS `lct_set` (
      `parent_uid` varchar(48) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL,
      `ref_uid` varchar(48) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL,
      PRIMARY KEY (`parent_uid`,`ref_uid`),
      UNIQUE KEY `BACK_PRIMARY` (`ref_uid`,`parent_uid`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    
    CREATE TABLE IF NOT EXISTS `ct_pricelistentry` (
      `uid` varchar(48) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL,
      `refcount` int(11) NOT NULL,
      `isDisposed` tinyint(1) DEFAULT NULL,
      `tag` text,
      `isGroup` tinyint(1) DEFAULT NULL,
      `parentEntry` varchar(48) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL,
      `externalUID` varchar(255) DEFAULT NULL,
      `productCode` varchar(16) DEFAULT NULL,
      `name` varchar(255) DEFAULT NULL,
      `sku` varchar(255) DEFAULT NULL,
      `skuGroup` varchar(255) DEFAULT NULL,
      `measureUnit` varchar(16) DEFAULT NULL,
      `image` varchar(48) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL,
      `itemClassExternalUID` varchar(255) DEFAULT NULL,
      `itemClassName` varchar(255) DEFAULT NULL,
      `itemClassDescription` text,
      `itemClassComments` text,
      `itemClassAttachments` varchar(48) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL,
      `brand` varchar(48) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL,
      `priceGroups` text,
      `productAttributes` text,
      `constituents` text,
      `position` int(11) DEFAULT NULL,
      `isService` tinyint(1) DEFAULT NULL,
      `stackability` varchar(255) DEFAULT NULL,
      PRIMARY KEY (`uid`),
      UNIQUE KEY `test1` (`uid`,`skuGroup`),
      KEY `name` (`name`),
      KEY `sku` (`sku`),
      KEY `itemClassExternalUID` (`itemClassExternalUID`),
      KEY `parentEntry` (`parentEntry`),
      KEY `position` (`position`),
      KEY `externalUID` (`externalUID`),
      KEY `productCode` (`productCode`),
      KEY `skuGroup` (`skuGroup`),
      KEY `brand` (`brand`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    

1 个答案:

答案 0 :(得分:2)

修复使用INDEX(skuGroup)可以避免文件排序,但会阻止任何有用的过滤。优化过滤比避免文件排序更重要。

删除FORCE并添加此'复合'索引

INDEX(isGroup, isService, brand) -- (in any order)

它应该有所帮助,但可能不会阻止“使用filesort”。 OR是杀手锏。

为防止对ORDER BY使用filesort,您需要一个(通常是复合的)索引,其中包含WHERE子句的 all plus ORDER BY列。在构建这样的索引时,WHERE中可以处理的事物和'='子句一起使用。其他任何内容(例如您的OR)都会阻止优化。

为什么OR伤害以这种方式思考...假设有一个长姓列名的姓名和名字的名单。查询询问WHERE last = 'Karakulov' ORDER BY first。你会跳到第一个卡拉库洛夫,并且会有所有的名字。现在假设您想要WHERE (last = 'Karakulov' OR last = 'James') ORDER BY first。你可以得到你所有的亲戚和我所有的亲戚,但是你仍然需要将它们混合起来做ORDER BY first。 MySQL有一种技术:filesort(以及一个导致它的tmp表。)

作为一种安慰,filesort的临时表通常是一个内存中的MEMORY表,所以速度相当快。

解决方法是有时OR变为UNION。 (这可能对您的查询没有帮助。)

一些架构批评和其他注释......

UNIQUE密钥没用,因为PRIMARY KEY已将uid声明为“唯一”。

VARCHAR(48) utf8是一个相当笨拙的大钥匙。它是某种形式的UUID吗?如果是这样的话,我就有关随机性和字符集和大小的说法有些令人讨厌。

有些uid是(48),有些是(255);这是故意的吗?

摆脱( skuGroup IS NULL OR skuGroup IS NOT NULL) - 优化工具可能不够聪明,无法意识到这总是“正确”!

FORCE INDEX今天可能有用,但明天会适得其反。摆脱它。

innodb_buffer_pool_size的价值是多少?如果你有至少4GB的RAM,它应该是可用 RAM的70%左右。如果你把它保留在某个低默认值,那么你可能是I / O绑定的,因此很慢。

请提供SHOW CREATE TABLE lct_set - JOIN中发生了一些奇怪的事情。