【问题标题】:MySQL ORDER BY using filesort (2 joined tables)MySQL ORDER BY 使用文件排序(2 个连接表)
【发布时间】:2016-11-29 19:37:45
【问题描述】:

我在查询优化方面遇到了奇怪的问题。 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

解释说:

'1', 'SIMPLE', 'ct_pricelistentry', 'ALL', 'PRIMARY', NULL, NULL, NULL, '34591', '使用where;使用文件排序'

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

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

经过一些研究,我发现了 hacky 解决方案,但不确定它是否能正常工作:

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

以下查询

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,所以它似乎使用索引来获取有序行:

'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', '使用where;使用 索引'

到底发生了什么? 它是一个 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;

【问题讨论】:

  • 您是否对这两个查询进行了基准测试以查看哪个更快?文件排序并不总是最糟糕的选择。
  • 是的,使用文件排序 0.0790 秒。 vs index fetch 0.0041 s.
  • volume - 结果集中有 20220 行
  • 使用skuGroup 作为索引实际上应该没有帮助 - 它仍然需要从表中获取所有行来检查条件,但现在是随机访问顺序,所以它实际上应该更慢。您能否再次测试您的两个查询,但以不同的顺序(第二个在前),所以您的减速带不是由缓冲产生的?假设brandct_pricelistentry 的一部分,而uuid 是主键(并且猜测您的布尔值不是很独特),我建议在brand 上使用索引,并且为了防止文件排序,请使用复合索引@987654336 @.
  • 我使用了 SQL_NO_CACHE 并以不同的顺序尝试了它们 - 使用文件排序总是较慢。 brand 上的索引有效,但实际上只有 3 个品牌,它不像布尔值那样与众不同。是的,当使用 skuGroup 时,它必须获取所有行,但至少它们已经按需要的顺序排列(我不明白多么随机可能会发生访问读取?除了低级细节)

标签: mysql sql database query-optimization


【解决方案1】:

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

删除FORCE 并添加此“复合”索引

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

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

为了防止对ORDER BY 使用文件排序,您需要一个包含WHERE 子句的全部 的单个(通常是复合)索引,加上 @ 987654327@ 列。在构造这样的索引时,WHERE 中可以处理的唯一 项是“=”子句。其他任何内容(例如您的OR)都会阻止优化。

为什么 OR 会受伤这样想……假设有一个按姓氏 + 名字排序的长打印姓名列表。查询要求WHERE last = 'Karakulov' ORDER BY first。你会跳到第一个 Karakulov,所有的名字都会按顺序排列。现在假设你想要WHERE (last = 'Karakulov' OR last = 'James') ORDER BY first。你可以得到你所有的亲戚和我所有的亲戚,但你仍然需要把他们洗牌来做ORDER BY first。 MySQL 有一种技术:文件排序(和一个通往它的 tmp 表。)

作为安慰,filesort 的临时表通常是内存中的 MEMORY 表,因此速度相当快。

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

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

UNIQUE 键没有用,因为 PRIMARY KEY 已经声明 uid 是“唯一的”。

VARCHAR(48) utf8 是一个相当笨拙的大键。它是某种形式的 UUID 吗?如果是这样,我对随机性、字符集和大小有一些讨厌的话要说。

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

摆脱(skuGroupIS NULL ORskuGroupIS NOT NULL)——优化器可能不够聪明,无法意识到这总是“正确的”!

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

innodb_buffer_pool_size 的值是多少?如果您有至少 4GB 的 RAM,它应该是大约 70% 的 可用 RAM。如果您将其保留在某个较低的默认值,那么您可能会受到 I/O 限制,因此速度很慢。

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

【讨论】:

  • 感谢您的回答。过滤条件是动态形成的,创建所有可能的组合是不切实际的......无论如何,单独使用 skuGroup 索引来进行 ORDER BY 是 1)可能 2)将查询加速大约 5 倍......没有理由限制这一点。当然在其他情况下可能会更慢,然后我选择其他策略。
  • 由于缓存,我不相信该索引真的更快。可能是当你运行它的时候,所有的东西都被缓存了,所以那个时候它跑得很快。
  • 应该为生成(skuGroupIS NULL ORskuGroupIS NOT NULL) 拍摄该ORM。 (我花了太多时间帮助人们解决第 3 方软件包产生的混乱。)我希望 ORM 给你足够的控制权来解决我的其他一些建议。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-08-09
  • 2010-11-29
  • 2015-11-05
  • 1970-01-01
  • 2011-09-10
  • 2010-10-18
  • 1970-01-01
相关资源
最近更新 更多