我在查询优化方面遇到了奇怪的问题。 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解决方案,但不确定它是否有效:
FORCE INDEX (skuGroup)
,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没有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;
答案 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
中发生了一些奇怪的事情。