添加条件后出现严重的 MySQL 查询性能问题
Serious MySQL query performance issues after adding condition
我的问题是我有一个运行速度非常快(0.3 秒)的 mysql 查询,即使它有大量的左连接和连接列上的一些条件,但是当我添加一个更多条件查询需要超过 180 秒!我知道这个条件意味着执行计划必须先调整以提取所有潜在记录,然后在循环中应用该条件,但对我来说很奇怪的是没有附加条件的快速查询只有 returns 16 行,甚至只是用外部查询的条件包装查询也会花费大量时间,而您认为它只会在 16 行中添加一个额外的循环...
如果重要的是使用 Amazon Aurora 无服务器,它应该符合 mysql 5.7
查询如下所示。您可以看到附加条件被注释掉了。 (数据库本身的一般 table 结构目前无法更改,因此请不要建议进行完整的数据库重组)
select
e1.entityId as _id,
v1.Value,
v2.Value
v3.Value,
v4.Value,
v5.Value,
v6.Value,
v7.Value,
v8.Value,
v9.Value,
v10.Value,
v11.Value,
v12.Value
from entity e1
left join val as v1 on (v1.entityId = e1.entityId and v1.attributeId = 1189)
left join val as v2 on (v2.entityId = e1.entityId and v2.attributeId = 1190)
left join entity as e2 on e2.entityId = (select entityId from entity where code = v1.Value and type = 88 limit 1)
left join val as v3 on (v3.entityId = e2.entityId and v3.attributeId = 507)
left join val as v4 on (v4.entityId = e2.entityId and v4.attributeId = 522)
left join val as v5 on (v5.entityId = e2.entityId and v5.attributeId = 558)
left join val as v6 on (v6.entityId = e2.entityId and v6.attributeId = 516)
left join val as v7 on (v7.entityId = e2.entityId and v7.attributeId = 518)
left join val as v8 on (v8.entityId = e2.entityId and v8.attributeId = 1384)
left join val as v9 on (v9.entityId = e2.entityId and v9.attributeId = 659)
left join val as v10 on (v10.entityId = e2.entityId and v10.attributeId = 519)
left join val as v11 on (v11.entityId = e2.entityId and v11.attributeId = 1614)
left join entity as e3 on e3.entityId = (select entityId from entity where code = v9.Value and type = 97 limit 1)
left join val as v12 on (v12.entityId = e3.entityId and v12.attributeId = 661)
where e1.type = 154
and v2.Value = 'foo'
and v5.Value = 'bar'
and v10.Value = 'foo2'
-- and v11`.Value = 'bar2'
order by v3.Value asc;
用这样的方式包装它仍然需要很长时间...
select *
from (
<query from above>
) sub
where sub.v11 = 'bar2';
条件注释掉的查询执行计划(快)
包含条件的查询执行计划(慢)
我将 fiddle 围绕“实体”建立索引 tables 以改进执行计划,无论哪个可能会有所帮助......但是有人可以解释这里发生了什么以及我应该在表明如此糟糕表现的执行计划中查看什么?为什么将快速查询包装在子查询中,以便外部查询只循环 16 行需要很长时间?
编辑:我注意到在慢速查询中最左边的执行正在使用非唯一键查找(在val.entityId上)“68e9145e-43eb-4581-9727-4212be41bef5”(v11 ) 而不是其余使用的唯一键查找(这是 entityId、attributeId 上的复合索引)。我想这可能是问题的一部分,但为什么它不能像其他地方那样使用复合索引?
PS:现在因为我们知道结果集会很小,我们正在实现最后一个条件服务器端,在我们的 nodeJS 服务器中对结果集进行过滤器。
这是“SHOW CREATE TABLE entity”和“SHOW CREATE TABLE val”的结果
CREATE TABLE `entity` (
`entityId` int(11) NOT NULL AUTO_INCREMENT,
`UID` varchar(64) NOT NULL,
`type` int(11) NOT NULL,
`code` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci
PRIMARY KEY (`entityId`),
UNIQUE KEY `UID` (`UID`),
KEY `IX_Entity_Type` (`type`),
CONSTRAINT `FK_Entities_Types` FOREIGN KEY (`type`) REFERENCES `entityTypes` (`typeId`) ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE=InnoDB AUTO_INCREMENT=296138 DEFAULT CHARSET=latin1
CREATE TABLE `val` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`UID` varchar(64) NOT NULL,
`attributeId` int(11) NOT NULL,
`entityId` int(11) NOT NULL,
`Value` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci
PRIMARY KEY (`id`),
UNIQUE KEY `UID` (`UID`),
UNIQUE KEY `idx_val_entityId_attributeId` (`entityId`,`attributeId`),
KEY `IX_val_attributeId` (`attributeId`),
KEY `IX_val_entityId` (`entityId`)
) ENGINE=InnoDB AUTO_INCREMENT=2325375 DEFAULT CHARSET=latin1
请提供SHOW CREATE TABLE
.
我希望看到这些复合索引:
`val`: (entityId, attributeId) -- order is not critical
唉,因为code
是LONGTEXT
,这对entity
来说是不可能的:INDEX(type, code, entityId)
。因此这不会很有效:
SELECT entityId
from entity
where code = v9.Value
and type = 97
limit 1
我看到 LIMIT
有一个 ORDER BY
-- 你关心你得到的是哪个值吗?
可能这样写会更好
WHERE EXISTS ( SELECT 1 FROM entity
WHERE entityID = e3.entityID
AND code = v9.Value
AND type = 97 )
(你确定e3
和v9
的混合吗?)
正在包装...
这迫使 LEFT JOIN
变为 JOIN
。它摆脱了当时的内部 ORDER BY
.
然后优化器可能决定最好从 68e9145e-43eb-4581-9727-4212be41bef5
开始,我称之为 val AS v11
:
JOIN val AS v11 ON (v11.entityId = e2.id
and v11.attributeId = 1614)
AND v11.Value = 'bar2')
如果这是一个 EAV table,那么它所做的只是验证 [ 1514] 的值 'bar2'。这似乎不是一个明智的测试。
除了我之前的推荐。
我更喜欢EXPLAIN SELECT ...
.
EAV
假设 val
是传统的 EAV table,这可能会好得多:
CREATE TABLE `val` (
`attributeId` int(11) NOT NULL,
`entityId` int(11) NOT NULL,
`Value` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci
PRIMARY KEY(`entityId`,`attributeId`),
KEY `IX_val_attributeId` (`attributeId`),
) ENGINE=InnoDB AUTO_INCREMENT=2325375 DEFAULT CHARSET=latin1
这两个 ID 没有实际用途(除非我遗漏了什么)。如果你因为一个框架而被迫使用它们,那就太不幸了。将 (entityId, attributeId) 提升为 PK 使获取 value
快一点。
没有任何有用的方法可以在任何索引中包含 LONGTEXT
,因此我之前的一些建议需要更改。
我的问题是我有一个运行速度非常快(0.3 秒)的 mysql 查询,即使它有大量的左连接和连接列上的一些条件,但是当我添加一个更多条件查询需要超过 180 秒!我知道这个条件意味着执行计划必须先调整以提取所有潜在记录,然后在循环中应用该条件,但对我来说很奇怪的是没有附加条件的快速查询只有 returns 16 行,甚至只是用外部查询的条件包装查询也会花费大量时间,而您认为它只会在 16 行中添加一个额外的循环...
如果重要的是使用 Amazon Aurora 无服务器,它应该符合 mysql 5.7
查询如下所示。您可以看到附加条件被注释掉了。 (数据库本身的一般 table 结构目前无法更改,因此请不要建议进行完整的数据库重组)
select
e1.entityId as _id,
v1.Value,
v2.Value
v3.Value,
v4.Value,
v5.Value,
v6.Value,
v7.Value,
v8.Value,
v9.Value,
v10.Value,
v11.Value,
v12.Value
from entity e1
left join val as v1 on (v1.entityId = e1.entityId and v1.attributeId = 1189)
left join val as v2 on (v2.entityId = e1.entityId and v2.attributeId = 1190)
left join entity as e2 on e2.entityId = (select entityId from entity where code = v1.Value and type = 88 limit 1)
left join val as v3 on (v3.entityId = e2.entityId and v3.attributeId = 507)
left join val as v4 on (v4.entityId = e2.entityId and v4.attributeId = 522)
left join val as v5 on (v5.entityId = e2.entityId and v5.attributeId = 558)
left join val as v6 on (v6.entityId = e2.entityId and v6.attributeId = 516)
left join val as v7 on (v7.entityId = e2.entityId and v7.attributeId = 518)
left join val as v8 on (v8.entityId = e2.entityId and v8.attributeId = 1384)
left join val as v9 on (v9.entityId = e2.entityId and v9.attributeId = 659)
left join val as v10 on (v10.entityId = e2.entityId and v10.attributeId = 519)
left join val as v11 on (v11.entityId = e2.entityId and v11.attributeId = 1614)
left join entity as e3 on e3.entityId = (select entityId from entity where code = v9.Value and type = 97 limit 1)
left join val as v12 on (v12.entityId = e3.entityId and v12.attributeId = 661)
where e1.type = 154
and v2.Value = 'foo'
and v5.Value = 'bar'
and v10.Value = 'foo2'
-- and v11`.Value = 'bar2'
order by v3.Value asc;
用这样的方式包装它仍然需要很长时间...
select *
from (
<query from above>
) sub
where sub.v11 = 'bar2';
条件注释掉的查询执行计划(快)
包含条件的查询执行计划(慢)
我将 fiddle 围绕“实体”建立索引 tables 以改进执行计划,无论哪个可能会有所帮助......但是有人可以解释这里发生了什么以及我应该在表明如此糟糕表现的执行计划中查看什么?为什么将快速查询包装在子查询中,以便外部查询只循环 16 行需要很长时间?
编辑:我注意到在慢速查询中最左边的执行正在使用非唯一键查找(在val.entityId上)“68e9145e-43eb-4581-9727-4212be41bef5”(v11 ) 而不是其余使用的唯一键查找(这是 entityId、attributeId 上的复合索引)。我想这可能是问题的一部分,但为什么它不能像其他地方那样使用复合索引?
PS:现在因为我们知道结果集会很小,我们正在实现最后一个条件服务器端,在我们的 nodeJS 服务器中对结果集进行过滤器。
这是“SHOW CREATE TABLE entity”和“SHOW CREATE TABLE val”的结果
CREATE TABLE `entity` (
`entityId` int(11) NOT NULL AUTO_INCREMENT,
`UID` varchar(64) NOT NULL,
`type` int(11) NOT NULL,
`code` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci
PRIMARY KEY (`entityId`),
UNIQUE KEY `UID` (`UID`),
KEY `IX_Entity_Type` (`type`),
CONSTRAINT `FK_Entities_Types` FOREIGN KEY (`type`) REFERENCES `entityTypes` (`typeId`) ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE=InnoDB AUTO_INCREMENT=296138 DEFAULT CHARSET=latin1
CREATE TABLE `val` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`UID` varchar(64) NOT NULL,
`attributeId` int(11) NOT NULL,
`entityId` int(11) NOT NULL,
`Value` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci
PRIMARY KEY (`id`),
UNIQUE KEY `UID` (`UID`),
UNIQUE KEY `idx_val_entityId_attributeId` (`entityId`,`attributeId`),
KEY `IX_val_attributeId` (`attributeId`),
KEY `IX_val_entityId` (`entityId`)
) ENGINE=InnoDB AUTO_INCREMENT=2325375 DEFAULT CHARSET=latin1
请提供SHOW CREATE TABLE
.
我希望看到这些复合索引:
`val`: (entityId, attributeId) -- order is not critical
唉,因为code
是LONGTEXT
,这对entity
来说是不可能的:INDEX(type, code, entityId)
。因此这不会很有效:
SELECT entityId
from entity
where code = v9.Value
and type = 97
limit 1
我看到 LIMIT
有一个 ORDER BY
-- 你关心你得到的是哪个值吗?
可能这样写会更好
WHERE EXISTS ( SELECT 1 FROM entity
WHERE entityID = e3.entityID
AND code = v9.Value
AND type = 97 )
(你确定e3
和v9
的混合吗?)
正在包装...
这迫使 LEFT JOIN
变为 JOIN
。它摆脱了当时的内部 ORDER BY
.
然后优化器可能决定最好从 68e9145e-43eb-4581-9727-4212be41bef5
开始,我称之为 val AS v11
:
JOIN val AS v11 ON (v11.entityId = e2.id
and v11.attributeId = 1614)
AND v11.Value = 'bar2')
如果这是一个 EAV table,那么它所做的只是验证 [ 1514] 的值 'bar2'。这似乎不是一个明智的测试。
除了我之前的推荐。
我更喜欢EXPLAIN SELECT ...
.
EAV
假设 val
是传统的 EAV table,这可能会好得多:
CREATE TABLE `val` (
`attributeId` int(11) NOT NULL,
`entityId` int(11) NOT NULL,
`Value` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci
PRIMARY KEY(`entityId`,`attributeId`),
KEY `IX_val_attributeId` (`attributeId`),
) ENGINE=InnoDB AUTO_INCREMENT=2325375 DEFAULT CHARSET=latin1
这两个 ID 没有实际用途(除非我遗漏了什么)。如果你因为一个框架而被迫使用它们,那就太不幸了。将 (entityId, attributeId) 提升为 PK 使获取 value
快一点。
没有任何有用的方法可以在任何索引中包含 LONGTEXT
,因此我之前的一些建议需要更改。