多个表的索引性能

时间:2015-02-03 15:21:10

标签: mysql sql indexing query-optimization

我在为生产提高查询速度方面遇到了一些麻烦。

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

重点是,当它们是给定enregistrement的最后一个(日期为YYYYMM)时,我需要获取所有periode条记录。 获取这些记录后,我想将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秒的执行时间。 有什么想法吗?

感谢。

4 个答案:

答案 0 :(得分:0)

不知道这是否会有所帮助,但你的情况是做什么......你总结了完全不同的领域,并将另一个领域计算为“总计”。我怀疑你可能真的希望这些作为他们自己的专栏。

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

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

并从查询中删除'position'表。这是对您开始的内容的略微修改的查询。我删除了位置引用,并且还更改了内部查询的“分组依据”,以便它可以更好地匹配列periode和salarie_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

答案 1 :(得分:0)

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

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列添加索引可能是一个良好的开端。我可能做的另一个建议是在单独的事务中运行此查询,并将结果插入临时表。这应该至少释放一些所需的资源,方法是将其转换为简单的连接,而不是在查询过程中进行非常复杂的搜索操作。如果这不起作用,您还可以尝试运行所有选择和连接而不加总和,将这些结果插入临时表,然后从那里选择和求和结果。

那就是说,没有看到你的表,每列中每个和所有数据的行数,你正在运行什么样的硬件,或者知道你的prod环境在使用方面是什么样的,它真的很难准确说出问题的确切位置。我非常确定MySQL中还没有内置函数,但是如果这对业务至关重要,那么使用Jet Profiler这样的查询分析可能是值得的。如果我正在编写一个崩溃生产服务器的查询,那么我将首先想要查看资源压力的确切位置。

答案 2 :(得分:0)

你的缓慢来自你对enregistrement的子选择。他们似乎都是表扫描看起来所有的记录。 IN也没有帮助。

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

enregistrement.etat_mouvement_id
enregistrement.periode
enregistrement.created_at

答案 3 :(得分:0)

在这里。我使用此查询将执行时间从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

由于我的sp索引:

,只需要执行0.003秒
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列,但我不知道是否可以在这两个字段上创建复合索引,因为它们来自不同的表。什么是更好的或替代解决方案?

感谢大家的回答:)