【问题标题】:Index performance on multiple tables多个表的索引性能
【发布时间】:2015-02-03 15:21:09
【问题描述】:

我在使查询更快用于生产方面遇到了一些麻烦。

我要执行的查询目前需要 12 秒才能显示结果集,并且它会导致资源受限的生产服务器崩溃。

关键是当enregistrement 是给定periode(日期为YYYYMM)中的最后一个时,我需要获取所有enregistrement 记录。 获得这些记录后,我想将I.sum_field 中的一个字段汇总为total 字段。

当我评论 CASE 部分时,查询大约需要 5 秒(+/- 500 毫秒)。

这里是查询:

SELECT 
      I.libelle, 
      E1.periode, 
      E1.created_at, 
      CASE WHEN I.sum_field = 'fat' THEN SUM(E1.Fat)
           WHEN I.sum_field = 'etp' THEN SUM(E1.Etp)
           WHEN I.sum_field = 'nb_ident' THEN COUNT(*)
           WHEN I.sum_field = 'cdi_actif' THEN SUM(E1.cdi_actif)
      END AS total
   FROM 
      indicateur_motif IM
         INNER JOIN indicateur I 
            ON IM.indicateur_id = I.id
         INNER JOIN `position` P 
            ON IM.motif_id = P.id
         INNER JOIN enregistrement E1 
            ON P.id = E1.position_id
            INNER JOIN 
               ( SELECT 
                       MAX(id) AS id, 
                       MAX(created_at) AS created_at
                    FROM 
                       enregistrement
                    WHERE 
                           (etat_mouvement_id IN (1,3,4))
                       AND (periode >= '201410' AND periode <= '201512')
                       AND created_at <= DATE_FORMAT('2015-02-03', '%Y-%m-%d %H:%i:%s')
                    GROUP BY 
                       salarie_id, 
                       periode ) E2 
               ON E1.id = E2.id 
               AND E1.created_at = E2.created_at
   WHERE 
      I.formule_id = 1
   GROUP BY 
      I.id, 
      E1.periode
   ORDER BY 
      I.position, 
      E1.periode

这是EXPLAIN 结果:

id  select_type  table           type    possible_keys                                   key                                             key_len  ref                   rows  Extra                                               
------  -----------  --------------  ------  ----------------------------------------------  ----------------------------------------------  -------  ------------------  ------  ----------------------------------------------------
 1  PRIMARY      I               ALL     PRIMARY                                         (NULL)                                          (NULL)   (NULL)                  21  Using where; Using temporary; Using filesort        
 1  PRIMARY      IM              ref     indicateur_motif_indicateur_id_motif_id_unique  indicateur_motif_indicateur_id_motif_id_unique  4        orhase.I.id              2  Using index                                         
 1  PRIMARY      P               eq_ref  PRIMARY                                         PRIMARY                                         4        orhase.IM.motif_id       1  Using index                                         
 1  PRIMARY      <derived2>      ALL     (NULL)                                          (NULL)                                          (NULL)   (NULL)              165352  Using where; Using join buffer (Block Nested Loop)  
 1  PRIMARY      e1              eq_ref  PRIMARY                                         PRIMARY                                         4        e2.id                    1  Using where                                         
 2  DERIVED      enregistrement  index   sp                                              sp                                              771      (NULL)              165352  Using where                                         

这是结果集的示例:

libelle                                     periode           created_at  total    
------------------------------------------  -------  -------------------  ---------
CDI actifs fin de période                   201410   2014-10-01 00:00:00  4689     
CDI actifs fin de période                   201411   2015-01-29 08:12:03  4674     
CDI actifs fin de période                   201412   2015-01-29 08:12:03  4660     
CDI actifs fin de période                   201501   2015-01-29 08:12:04  4444     
CDI actifs fin de période                   201502   2015-01-29 08:12:04  4222     
CDI actifs fin de période                   201503   2015-01-29 08:12:04  4195     
CDI actifs fin de période                   201504   2015-01-29 08:12:04  4176     
CDI actifs fin de période                   201505   2015-01-29 08:12:04  4155     
CDI actifs fin de période                   201506   2015-01-29 08:12:04  4136     
CDI actifs fin de période                   201507   2015-01-29 08:12:04  4121     
CDI actifs fin de période                   201508   2015-01-29 08:12:04  4080     
CDI actifs fin de période                   201509   2015-01-29 08:12:04  4061     
CDI actifs fin de période                   201510   2015-01-29 08:12:04  4036     
CDI actifs fin de période                   201511   2015-01-29 08:12:04  4001     
CDI actifs fin de période                   201512   2015-01-29 08:12:04  3976     
ETP fin de période CDI stock                201410   2014-10-01 00:00:00  4259.16  
ETP fin de période CDI stock                201411   2015-01-29 08:12:03  4241.91  
ETP fin de période CDI stock                201412   2015-01-29 08:12:03  4222.12  
ETP fin de période CDI stock                201501   2015-01-29 08:12:04  4028.07  

我只是不知道在哪里放置一个新索引以避免这个执行时间......我已经在 enregistrement 上放置了一个,称为 sp

ALTER TABLE enregistrement ADD INDEX sp(salarie_id, periode);

这让我得到一个从 16 秒到 12 秒的执行时间。 有什么想法吗?

谢谢。

【问题讨论】:

  • 这个查询真的执行了吗???
  • 是的,但这非常慢,正如我所说的......

标签: mysql sql indexing query-optimization


【解决方案1】:

不知道这是否会有所帮助,但是您的情况是什么...您将完全不同的字段相加并将另一个字段计入“总计”。我怀疑你可能真的想要这些作为他们自己的专栏。

但是,话虽如此,您有什么索引...您的解释显示了一些,但如果它们不可用,我会尝试包括以下内容...

table             index 
indicateur        ( formule_id, id, position )
indicateur_motif  ( indicateur_id, motif_id )
`position`        ( id )
enregistrement    ( position_id, id, created_at )  <-- for the JOIN portion
enregistrement    ( etat_mouvement_id, periode, created_at, salarie_id, id )  <-- for sub-select query

此外,从您的联接来看,您并没有真正使用“位置”表中的任何内容。是的,你从主题加入到位置,从位置加入到 enreg,但是自从

IM.motif_id = P.id  and  P.id = E1.position_id

那你就可以直接跳了

IM.motif_id = E1.position_id

并从查询中删除“位置”表。这是对您开始的内容的略微修改的查询。我删除了位置引用,还更改了内部查询的“分组依据”,以便与列 periode 和 salrie_id 的可用索引匹配可能会有更好的性能。

SELECT 
      I.libelle, 
      E1.periode, 
      E1.created_at, 
      CASE WHEN I.sum_field = 'fat' THEN SUM(E1.Fat)
           WHEN I.sum_field = 'etp' THEN SUM(E1.Etp)
           WHEN I.sum_field = 'nb_ident' THEN COUNT(*)
           WHEN I.sum_field = 'cdi_actif' THEN SUM(E1.cdi_actif)
      END AS total
   FROM 
      indicateur I 
         JOIN indicateur_motif IM
            ON I.id = IM.indicateur_id
            INNER JOIN enregistrement E1 
               ON IM.motif_id = E1.position_id
               INNER JOIN 
                  ( SELECT 
                          MAX(id) AS id, 
                          MAX(created_at) AS created_at
                       FROM 
                          enregistrement
                       WHERE 
                              etat_mouvement_id IN (1,3,4)
                          AND periode >= '201410' 
                          AND periode <= '201512'
                          AND created_at <= '2015-02-03'
                       GROUP BY 
                          periode,
                          salarie_id ) E2 
                  ON E1.id = E2.id 
                  AND E1.created_at = E2.created_at
   WHERE 
      I.formule_id = 1
   GROUP BY 
      I.id, 
      E1.periode
   ORDER BY 
      I.position, 
      E1.periode

【讨论】:

  • 你在position 连接上完全正确,我刚刚修补了这个。现在,我尝试了您的索引和查询建议,但结果相同或更糟。 (大约 12~16 秒执行)。你可以查看我的答案,我得到了一个新版本的查询,执行大约需要 6.7 秒,只有一个 INDEX。
  • @user2236078,位置很好,新查询到 7 秒……但还是太慢了,对吧???您尝试提取的内容的总体基础是什么(基于对外部上下文的内部查询)。
【解决方案2】:

我不知道你的表是什么样的,但是这个查询:

SELECT MAX(id) AS id, MAX(created_at) AS created_at
FROM enregistrement

WHERE (etat_mouvement_id IN (1,3,4))
AND (periode >= '201410' AND periode <= '201512')
AND created_at <= DATE_FORMAT('2015-02-03', '%Y-%m-%d %H:%i:%s')
GROUP BY salarie_id, periode

非常昂贵。如果您想尝试仅通过索引来解决此问题,向idcreated_at 列添加索引可能是一个好的开始。我可能提出的另一个建议是在单独的事务中运行此查询,并将结果插入临时表中。这至少应该通过将其转换为简单的连接而不是查询中间非常复杂的搜索操作来释放一些所需的资源。如果这不起作用,您还可以尝试运行所有不带总和的选择和连接,将这些结果插入到临时表中,然后从那里选择和求和结果。

也就是说,没有看到您的表格、每列中每个数据的行数、您正在运行的硬件类型,或者不知道您的产品环境在使用方面是什么样的,它真的很难说问题可能出在哪里。我很确定 MySQL 中还没有内置函数,但是如果这对业务至关重要,那么使用 Jet Profiler 之类的东西来分析查询可能是值得的。如果我正在编写一个导致生产服务器崩溃的查询,那么我首先要做的就是了解资源压力的确切来源。

【讨论】:

  • 我完全同意你的看法,但是:子查询在添加索引后并没有那么昂贵(见我的回答),每次执行只需要 0.003 秒。其他点我的表很简单:enregistrement 表中有大约 160k 插入,其他大约 10~200 插入。谢谢您的回答。 :)
【解决方案3】:

您的缓慢来自您在注册时的子选择。他们似乎都在扫描看起来所有记录的表格。 IN 也无济于事。

尝试在下表字段上创建索引并告诉我。

enregistrement.etat_mouvement_id
enregistrement.periode
enregistrement.created_at

【讨论】:

  • 添加单个列索引的价值低于基于条件、分组和排序条件的复合索引(多个字段作为一个索引)。
  • 我的建议是简单地添加索引,你是正确的复合索引在这种情况下肯定会带来更好的查询速度。
  • 感谢您的建议。正如我在回答中提到的,只有一个复合索引,我将执行时间减少了 2。现在,我需要减少 GROUP BY 和 CASE 命令所需的时间。正如我在许多文章中看到的那样,创建无用的 INDEX 条目不利于性能,所以我只想针对需要的正确 INDEX。
【解决方案4】:

来了。我使用这个查询将执行时间从 12 秒减少到 6.8 秒:

SELECT I.libelle, e1.periode,
    CASE WHEN I.sum_field = 'fat'  THEN SUM(E1.Fat)
         WHEN I.sum_field = 'etp'  THEN SUM(E1.Etp)
         WHEN I.sum_field = 'nb_ident'  THEN COUNT(*)
         WHEN I.sum_field = 'cdi_actif' THEN SUM(E1.cdi_actif) END AS 'total'

        FROM indicateur_motif IM
        INNER JOIN indicateur I ON IM.indicateur_id = I.id
        INNER JOIN enregistrement e1 ON IM.motif_id = e1.position_id
        INNER JOIN 
        (
            SELECT MAX(created_at) AS createdat, salarie_id, periode
            FROM enregistrement 
            WHERE  (etat_mouvement_id IN (1,3,4))
            AND (periode >= '201410' AND periode <= '201512')
            AND created_at <= DATE_FORMAT('2015-02-03', '%Y-%m-%d %H:%i:%s')
            GROUP BY salarie_id, periode
        ) e2 ON (e1.created_at = e2.createdat AND e1.salarie_id = e2.salarie_id AND e1.periode = e2.periode)

    WHERE I.formule_id = 1 
    GROUP BY I.id, e1.periode
    ORDER BY I.position, e1.periode

仅供参考,这个子查询:

SELECT MAX(created_at) AS createdat, salarie_id, periode
FROM enregistrement 
WHERE  (etat_mouvement_id IN (1,3,4))
AND (periode >= '201410' AND periode <= '201512')
AND created_at <= DATE_FORMAT('2015-02-03', '%Y-%m-%d %H:%i:%s')
GROUP BY salarie_id, periode

执行只需要 0.003 秒,感谢我的 sp 索引:

ALTER TABLE enregistrement ADD INDEX sp(salarie_id, periode);

@DRapp :您在我的 JOINS 上是对的,我从连接中删除了 position 并更正了查询。在总字段上,我确实想获取单个列上的值,而不是对我的代码逻辑做条件。

我尝试了 @DRapp 索引和查询命题,它们只是减慢或改变了我的查询。

id  select_type  table           type    possible_keys                                   key                                             key_len  ref                                  rows  Extra                                               
------  -----------  --------------  ------  ----------------------------------------------  ----------------------------------------------  -------  ---------------------------------  ------  ----------------------------------------------------
 1  PRIMARY      <derived2>      ALL     (NULL)                                          (NULL)                                          (NULL)   (NULL)                             165352  Using temporary; Using filesort                     
 1  PRIMARY      e1              ref     sp                                              sp                                              771      e2.salarie_id,e2.periode                1  Using where                                         
 1  PRIMARY      I               ALL     PRIMARY                                         (NULL)                                          (NULL)   (NULL)                                 21  Using where; Using join buffer (Block Nested Loop)  
 1  PRIMARY      IM              eq_ref  indicateur_motif_indicateur_id_motif_id_unique  indicateur_motif_indicateur_id_motif_id_unique  8        orhase.I.id,orhase.e1.position_id       1  Using index                                         
 2  DERIVED      enregistrement  index   sp                                              sp                                              771      (NULL)                             165352  Using where        

有了这个 EXPLAIN 结果,我想解析第一行,它描述了Using temporary; Using filesort。解决方案是索引 GROUP BY 列,但我不知道是否可以在这两个字段上创建复合索引,因为它们来自不同的表。什么是更好或替代的解决方案?

感谢大家的回答:)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-05-30
    • 2013-05-19
    • 2019-10-03
    • 2012-10-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-10-20
    相关资源
    最近更新 更多