MySQL 大量 INNER JOIN 查询导致响应缓慢
MySQL mass INNER JOIN queries cause slow response
我已经阅读了其他相关问题,但我的问题因其结构而独一无二。
我的应用程序存储了大约 10,000 多个用户,这些用户的配置文件由许多参数定义(性别、体重、身高、头发颜色、眼睛颜色、舞蹈技能……等等,大约 100 个属性)。
应用程序使用这些属性构建了一个过滤器表单。用户正在使用此表单过滤数据库,因此构造了一个包含许多子查询的查询,每个子查询对应一个过滤器。
问题是使用超过8-9个过滤器,引擎会崩溃到一个很长的响应(我不得不在等待30m后杀死进程)。
所以,这是数据库的结构
Table def_attributes(这里是属性定义)
- id ---> 在值 table
中用作 attr_id
Table utilizatori(用户定义,现在仅使用列 activ)
- id ---> 在 table 的其余部分被命名为 user_id
- activ ---> 如果用户处于活动状态则为 1 并将显示(列索引)
Table val_atribute(存储每个用户的属性值)
- attr_id ---> 过滤器的 attrID (列索引)
- attr_value ---> 属性值
- user_id (列索引)
例如,这是一个由过滤形式构造的查询,滞后于:
SELECT DISTINCT Q1.user_id
FROM (SELECT DISTINCT val_atribute.user_id
FROM val_atribute
WHERE attr_id = 45
AND attr_value IN ( 'Actor', 'Actor Amator' )) Q1
INNER JOIN (SELECT DISTINCT val_atribute.user_id
FROM val_atribute
WHERE attr_id = 46
AND Floor(Datediff(Curdate(), attr_value) / 365) >= '20') Q2
ON Q1.user_id = Q2.user_id
INNER JOIN (SELECT DISTINCT val_atribute.user_id
FROM val_atribute
WHERE attr_id = 46
AND Floor(Datediff(Curdate(), attr_value) / 365) <= '50') Q3
ON Q2.user_id = Q3.user_id
INNER JOIN (SELECT DISTINCT val_atribute.user_id
FROM val_atribute
WHERE attr_id = 47
AND attr_value IN ( 'feminin', 'masculin' )) Q4
ON Q3.user_id = Q4.user_id
INNER JOIN (SELECT DISTINCT val_atribute.user_id
FROM val_atribute
WHERE attr_id = 102
AND attr_value IN ( 'African', 'Asiatic', 'Caucazian', 'Metis' )) Q5
ON Q4.user_id = Q5.user_id
INNER JOIN (SELECT DISTINCT val_atribute.user_id
FROM val_atribute
WHERE attr_id = 103
AND attr_value >= 1) Q6
ON Q5.user_id = Q6.user_id
INNER JOIN (SELECT DISTINCT val_atribute.user_id
FROM val_atribute
WHERE attr_id = 103
AND attr_value <= 200) Q7
ON Q6.user_id = Q7.user_id
INNER JOIN (SELECT DISTINCT val_atribute.user_id
FROM val_atribute
WHERE attr_id = 104
AND attr_value >= 10) Q8
ON Q7.user_id = Q8.user_id
INNER JOIN (SELECT DISTINCT val_atribute.user_id
FROM val_atribute
WHERE attr_id = 104
AND attr_value <= 150) Q9
ON Q8.user_id = Q9.user_id
INNER JOIN (SELECT DISTINCT val_atribute.user_id
FROM val_atribute
WHERE attr_id = 107
AND attr_value IN ( 'Albastri', 'Caprui', 'Heterocrom', 'Verzi' )) Q10
ON Q9.user_id = Q10.user_id
INNER JOIN (SELECT DISTINCT val_atribute.user_id
FROM val_atribute
WHERE attr_id = 108
AND attr_value IN ( 'Blond', 'Brunet', 'Castaniu', 'Roscat', 'Saten' )) Q11
ON Q10.user_id = Q11.user_id
INNER JOIN (SELECT DISTINCT val_atribute.user_id
FROM val_atribute
WHERE attr_id = 109
AND attr_value IN ( 'Calvitie', 'Lung', 'Mediu', 'Scurt', 'Zero' )) Q12
ON Q11.user_id = Q12.user_id
INNER JOIN (SELECT DISTINCT utilizatori.id
FROM utilizatori
WHERE activ = 1) Q13
ON Q12.user_id = Q13.id
GROUP BY user_id
Q2 正在计算 AGE,因为我们只有属性 [出生日期] 并且过滤器 Q2 需要年龄 > 20。
最后一个查询(此处为 Q13)始终是来自 Table utilizatori 的数学活跃用户。
我认为是笛卡尔级数的问题,但是
问题: 我怎样才能重新生成查询以使其更快?
非常感谢!
编辑/问题解决:
在 Gordon Linoff 的大力帮助下,我使用相同的过滤器构建了正确的查询:
SELECT u.id
FROM utilizatori u
WHERE EXISTS (SELECT 1
FROM val_atribute va
WHERE va.user_id = u.id
AND va.attr_id = 45
AND attr_value IN ( 'Actor', 'Actor Amator' ))
AND EXISTS (SELECT 1
FROM val_atribute va
WHERE va.user_id = u.id
AND va.attr_id = 46
AND Floor(Datediff(Curdate(), attr_value) / 365) >= 20)
AND EXISTS (SELECT 1
FROM val_atribute va
WHERE va.user_id = u.id
AND va.attr_id = 46
AND Floor(Datediff(Curdate(), attr_value) / 365) <= 50)
AND EXISTS (SELECT 1
FROM val_atribute va
WHERE va.user_id = u.id
AND va.attr_id = 47
AND attr_value IN ( 'feminin', 'masculin' ))
AND EXISTS (SELECT 1
FROM val_atribute va
WHERE va.user_id = u.id
AND va.attr_id = 102
AND attr_value IN ( 'African', 'Asiatic', 'Caucazian', 'Metis' ))
AND EXISTS (SELECT 1
FROM val_atribute va
WHERE va.user_id = u.id
AND va.attr_id = 103
AND attr_value >= 1)
AND EXISTS (SELECT 1
FROM val_atribute va
WHERE va.user_id = u.id
AND va.attr_id = 103
AND attr_value <= 200)
AND EXISTS (SELECT 1
FROM val_atribute va
WHERE va.user_id = u.id
AND va.attr_id = 104
AND attr_value >= 10)
AND EXISTS (SELECT 1
FROM val_atribute va
WHERE va.user_id = u.id
AND va.attr_id = 104
AND attr_value <= 150)
AND EXISTS (SELECT 1
FROM val_atribute va
WHERE va.user_id = u.id
AND va.attr_id = 107
AND attr_value IN ( 'Albastri', 'Caprui', 'Heterocrom', 'Verzi' ))
AND EXISTS (SELECT 1
FROM val_atribute va
WHERE va.user_id = u.id
AND va.attr_id = 108
AND attr_value IN ( 'Blond', 'Brunet', 'Castaniu', 'Roscat', 'Saten' ))
AND EXISTS (SELECT 1
FROM val_atribute va
WHERE va.user_id = u.id
AND va.attr_id = 109
AND attr_value IN ( 'Calvitie', 'Lung', 'Mediu', 'Scurt', 'Zero' ))
AND activ = 1
现在查询 运行 大约需要 0.0015 秒。
MySQL 中的子查询有问题——select distinct
使事情变得更糟。您正在使用 and
连接子查询。我建议改为使用 exists
构建相同的逻辑。
所以:
select u.*
from users u
where exists (select 1
from val_atribute va
where va.user_id = u.user_id and
va.attr_id = 45 and
va.attr_value in ( 'Actor', 'Actor Amator' )
) and
exists (select 1
from val_atribute va
where va.user_id = u.user_id and
va.attr_id = 46 and
Floor(Datediff(Curdate(), va.attr_value) / 365) >= 20) Q2
) and
. . .
此版本的查询可以利用 val_attribute(user_id, attr_id, attr_value)
上的索引。它应该更快并且具有更好的可扩展性。
这是众所周知效率低下的 EAV 模式设计的变体。
到目前为止,最好的解决方案(在这个问题中)涉及对 utilizatori
的完整 table 扫描,并对属性 table (val_atribute
) 进行多次探测过滤。
为了提高效率,val_atribute
需要 PRIMARY KEY(user_id, attr_id)
。不,这两列的单独索引不如好。
为了提高效率,您需要提取 小 数量的 常用 属性并添加索引。这应该避免完整的 table 扫描(10K 用户,加上大量属性查找),将其减少到其中的一部分。
我已经阅读了其他相关问题,但我的问题因其结构而独一无二。
我的应用程序存储了大约 10,000 多个用户,这些用户的配置文件由许多参数定义(性别、体重、身高、头发颜色、眼睛颜色、舞蹈技能……等等,大约 100 个属性)。
应用程序使用这些属性构建了一个过滤器表单。用户正在使用此表单过滤数据库,因此构造了一个包含许多子查询的查询,每个子查询对应一个过滤器。
问题是使用超过8-9个过滤器,引擎会崩溃到一个很长的响应(我不得不在等待30m后杀死进程)。
所以,这是数据库的结构
Table def_attributes(这里是属性定义)
- id ---> 在值 table 中用作 attr_id
Table utilizatori(用户定义,现在仅使用列 activ)
- id ---> 在 table 的其余部分被命名为 user_id
- activ ---> 如果用户处于活动状态则为 1 并将显示(列索引)
Table val_atribute(存储每个用户的属性值)
- attr_id ---> 过滤器的 attrID (列索引)
- attr_value ---> 属性值
- user_id (列索引)
例如,这是一个由过滤形式构造的查询,滞后于:
SELECT DISTINCT Q1.user_id
FROM (SELECT DISTINCT val_atribute.user_id
FROM val_atribute
WHERE attr_id = 45
AND attr_value IN ( 'Actor', 'Actor Amator' )) Q1
INNER JOIN (SELECT DISTINCT val_atribute.user_id
FROM val_atribute
WHERE attr_id = 46
AND Floor(Datediff(Curdate(), attr_value) / 365) >= '20') Q2
ON Q1.user_id = Q2.user_id
INNER JOIN (SELECT DISTINCT val_atribute.user_id
FROM val_atribute
WHERE attr_id = 46
AND Floor(Datediff(Curdate(), attr_value) / 365) <= '50') Q3
ON Q2.user_id = Q3.user_id
INNER JOIN (SELECT DISTINCT val_atribute.user_id
FROM val_atribute
WHERE attr_id = 47
AND attr_value IN ( 'feminin', 'masculin' )) Q4
ON Q3.user_id = Q4.user_id
INNER JOIN (SELECT DISTINCT val_atribute.user_id
FROM val_atribute
WHERE attr_id = 102
AND attr_value IN ( 'African', 'Asiatic', 'Caucazian', 'Metis' )) Q5
ON Q4.user_id = Q5.user_id
INNER JOIN (SELECT DISTINCT val_atribute.user_id
FROM val_atribute
WHERE attr_id = 103
AND attr_value >= 1) Q6
ON Q5.user_id = Q6.user_id
INNER JOIN (SELECT DISTINCT val_atribute.user_id
FROM val_atribute
WHERE attr_id = 103
AND attr_value <= 200) Q7
ON Q6.user_id = Q7.user_id
INNER JOIN (SELECT DISTINCT val_atribute.user_id
FROM val_atribute
WHERE attr_id = 104
AND attr_value >= 10) Q8
ON Q7.user_id = Q8.user_id
INNER JOIN (SELECT DISTINCT val_atribute.user_id
FROM val_atribute
WHERE attr_id = 104
AND attr_value <= 150) Q9
ON Q8.user_id = Q9.user_id
INNER JOIN (SELECT DISTINCT val_atribute.user_id
FROM val_atribute
WHERE attr_id = 107
AND attr_value IN ( 'Albastri', 'Caprui', 'Heterocrom', 'Verzi' )) Q10
ON Q9.user_id = Q10.user_id
INNER JOIN (SELECT DISTINCT val_atribute.user_id
FROM val_atribute
WHERE attr_id = 108
AND attr_value IN ( 'Blond', 'Brunet', 'Castaniu', 'Roscat', 'Saten' )) Q11
ON Q10.user_id = Q11.user_id
INNER JOIN (SELECT DISTINCT val_atribute.user_id
FROM val_atribute
WHERE attr_id = 109
AND attr_value IN ( 'Calvitie', 'Lung', 'Mediu', 'Scurt', 'Zero' )) Q12
ON Q11.user_id = Q12.user_id
INNER JOIN (SELECT DISTINCT utilizatori.id
FROM utilizatori
WHERE activ = 1) Q13
ON Q12.user_id = Q13.id
GROUP BY user_id
Q2 正在计算 AGE,因为我们只有属性 [出生日期] 并且过滤器 Q2 需要年龄 > 20。
最后一个查询(此处为 Q13)始终是来自 Table utilizatori 的数学活跃用户。
我认为是笛卡尔级数的问题,但是 问题: 我怎样才能重新生成查询以使其更快? 非常感谢!
编辑/问题解决:
在 Gordon Linoff 的大力帮助下,我使用相同的过滤器构建了正确的查询:
SELECT u.id
FROM utilizatori u
WHERE EXISTS (SELECT 1
FROM val_atribute va
WHERE va.user_id = u.id
AND va.attr_id = 45
AND attr_value IN ( 'Actor', 'Actor Amator' ))
AND EXISTS (SELECT 1
FROM val_atribute va
WHERE va.user_id = u.id
AND va.attr_id = 46
AND Floor(Datediff(Curdate(), attr_value) / 365) >= 20)
AND EXISTS (SELECT 1
FROM val_atribute va
WHERE va.user_id = u.id
AND va.attr_id = 46
AND Floor(Datediff(Curdate(), attr_value) / 365) <= 50)
AND EXISTS (SELECT 1
FROM val_atribute va
WHERE va.user_id = u.id
AND va.attr_id = 47
AND attr_value IN ( 'feminin', 'masculin' ))
AND EXISTS (SELECT 1
FROM val_atribute va
WHERE va.user_id = u.id
AND va.attr_id = 102
AND attr_value IN ( 'African', 'Asiatic', 'Caucazian', 'Metis' ))
AND EXISTS (SELECT 1
FROM val_atribute va
WHERE va.user_id = u.id
AND va.attr_id = 103
AND attr_value >= 1)
AND EXISTS (SELECT 1
FROM val_atribute va
WHERE va.user_id = u.id
AND va.attr_id = 103
AND attr_value <= 200)
AND EXISTS (SELECT 1
FROM val_atribute va
WHERE va.user_id = u.id
AND va.attr_id = 104
AND attr_value >= 10)
AND EXISTS (SELECT 1
FROM val_atribute va
WHERE va.user_id = u.id
AND va.attr_id = 104
AND attr_value <= 150)
AND EXISTS (SELECT 1
FROM val_atribute va
WHERE va.user_id = u.id
AND va.attr_id = 107
AND attr_value IN ( 'Albastri', 'Caprui', 'Heterocrom', 'Verzi' ))
AND EXISTS (SELECT 1
FROM val_atribute va
WHERE va.user_id = u.id
AND va.attr_id = 108
AND attr_value IN ( 'Blond', 'Brunet', 'Castaniu', 'Roscat', 'Saten' ))
AND EXISTS (SELECT 1
FROM val_atribute va
WHERE va.user_id = u.id
AND va.attr_id = 109
AND attr_value IN ( 'Calvitie', 'Lung', 'Mediu', 'Scurt', 'Zero' ))
AND activ = 1
现在查询 运行 大约需要 0.0015 秒。
MySQL 中的子查询有问题——select distinct
使事情变得更糟。您正在使用 and
连接子查询。我建议改为使用 exists
构建相同的逻辑。
所以:
select u.*
from users u
where exists (select 1
from val_atribute va
where va.user_id = u.user_id and
va.attr_id = 45 and
va.attr_value in ( 'Actor', 'Actor Amator' )
) and
exists (select 1
from val_atribute va
where va.user_id = u.user_id and
va.attr_id = 46 and
Floor(Datediff(Curdate(), va.attr_value) / 365) >= 20) Q2
) and
. . .
此版本的查询可以利用 val_attribute(user_id, attr_id, attr_value)
上的索引。它应该更快并且具有更好的可扩展性。
这是众所周知效率低下的 EAV 模式设计的变体。
到目前为止,最好的解决方案(在这个问题中)涉及对 utilizatori
的完整 table 扫描,并对属性 table (val_atribute
) 进行多次探测过滤。
为了提高效率,val_atribute
需要 PRIMARY KEY(user_id, attr_id)
。不,这两列的单独索引不如好。
为了提高效率,您需要提取 小 数量的 常用 属性并添加索引。这应该避免完整的 table 扫描(10K 用户,加上大量属性查找),将其减少到其中的一部分。