【发布时间】: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 解决方案,但不确定它是否能正常工作:
- 加
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,所以它似乎使用索引来获取有序行:
'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作为索引实际上应该没有帮助 - 它仍然需要从表中获取所有行来检查条件,但现在是随机访问顺序,所以它实际上应该更慢。您能否再次测试您的两个查询,但以不同的顺序(第二个在前),所以您的减速带不是由缓冲产生的?假设brand是ct_pricelistentry的一部分,而uuid是主键(并且猜测您的布尔值不是很独特),我建议在brand上使用索引,并且为了防止文件排序,请使用复合索引@987654336 @. -
我使用了 SQL_NO_CACHE 并以不同的顺序尝试了它们 - 使用文件排序总是较慢。
brand上的索引有效,但实际上只有 3 个品牌,它不像布尔值那样与众不同。是的,当使用 skuGroup 时,它必须获取所有行,但至少它们已经按需要的顺序排列(我不明白多么随机可能会发生访问读取?除了低级细节)
标签: mysql sql database query-optimization