【问题标题】:MySQL mass INNER JOIN queries cause slow responseMySQL 大量 INNER JOIN 查询导致响应缓慢
【发布时间】:2017-11-21 11:39:35
【问题描述】:

我已经阅读了其他相关问题,但我的问题是独一无二的,因为它的结构。

我的应用程序存储了大约 10,000 多名用户,这些用户的个人资料由许多参数(性别、体重、身高、头发颜色、眼睛颜色、跳舞技巧……等等,大约 100 个属性,比如说)定义。

应用程序使用这些属性构造一个过滤器表单。用户正在使用此表单过滤数据库,因此构造一个包含许多子查询的查询,每个过滤器对应一个。

问题是使用超过 8-9 个过滤器,引擎会崩溃到一个很长的响应(我不得不在等待 30m 后终止进程)。

所以,这就是数据库的结构

表 def_attributes(这里是属性定义)

  • id ---> 在值表中用作 attr_id

Table utilizatori(用户定义,现在只使用 activ 列)

  • id ---> 在其余表中被命名为 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,因为我们只有属性 [Date ofbirth] 并且过滤器 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 秒。

【问题讨论】:

  • 你选择什么,为什么要分组?
  • 我在最后一个查询中选择活动用户 (user_id),匹配 12 个过滤器,为每个用户存储在表 val_atribute 中
  • 如果@GordonLinoff 的回答解决了您的问题,请将其标记为已接受。如果您对答案有所改进,请添加您的own answer,而不是将其添加到问题中。您也可以accept your own answer,但您必须等待 48 小时才能这样做。
  • 再次检查 -- 1.5ms 闻起来就像您运行了两次查询并且“查询缓存”已打开。在SELECT 之后添加SQL_NO_CACHE
  • @Rick James 你是对的,使用 SQL_NO_CACHE 查询花费了 0.0055 秒,是 3 倍,但在我看来仍然非常好

标签: mysql sql inner-join query-performance entity-attribute-value


【解决方案1】:

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) 上的索引。它应该更快并且具有更好的可扩展性。

【讨论】:

  • 非常感谢!您的解决方案非常出色!使用完全相同的过滤器,现在查询耗时 0.0015 秒。
  • 从 30 秒到 0.0015 秒。 . .这是 99.995% 的加速。
【解决方案2】:

这是众所周知的低效 EAV 架构设计的变体。

到目前为止,最好的解决方案(在这个问题中)涉及对utilizatori 的全表扫描,其中对属性表 (val_atribute) 进行许多探测以进行过滤。

为了提高效率,val_atribute 需要 PRIMARY KEY(user_id, attr_id)。不,这两列上的单独索引不是那么好。

为了提高效率,您需要提取 smallcommonly 使用的属性并添加一个索引。这应该避免全表扫描(10K 用户,加上大量属性查找),将其减少到其中的一小部分。

更多讨论:http://mysql.rjweb.org/doc.php/eav

【讨论】:

  • 这绝对是一个很好的方法,我看到了该方法的论点和潜力,但由于我没有看到任何 DB 结构示例(只是一种理论方法),我想问你:我们创建的大表,将存储所有数据用户/过滤器/值?;每个过滤器/user_id/etc./ 都有一列,每个用户有一行?谢谢。
  • 一个 few 索引列,然后是所有“100 个属性”的大型 JSON 结构。列和索引将是 first 过滤。然后应用程序在解压缩 JSON 后执行第二次过滤。所有属性都将在表中——大约有十几个列的 10K 行,每行可能 2KB。加起来就是一个“小”的 20MB 表。
猜你喜欢
  • 2011-03-02
  • 1970-01-01
  • 1970-01-01
  • 2015-11-27
  • 1970-01-01
  • 1970-01-01
  • 2019-02-03
  • 2014-07-07
  • 2013-12-22
相关资源
最近更新 更多