使用 where 子句的慢速计数查询

Slow count query with where clause

我正在尝试执行计数以获取分页中的结果总数,但查询速度太慢 2.12s

+-------+
| size  |
+-------+
| 50000 |
+-------+
1 row in set (2.12 sec)

我的计数查询

select  count(appeloffre0_.ID_APPEL_OFFRE) as size  
from  ao.appel_offre appeloffre0_  
inner join ao.acheteur acheteur1_ 
on appeloffre0_.ID_ACHETEUR=acheteur1_.ID_ACHETEUR 
where 
(exists (select 1 from ao.lot lot2_ where lot2_.ID_APPEL_OFFRE=appeloffre0_.ID_APPEL_OFFRE and lot2_.ESTIMATION_COUT>=1)) 
and (exists (select 1 from ao.lieu_execution lieuexecut3_ where lieuexecut3_.appel_offre=appeloffre0_.ID_APPEL_OFFRE and lieuexecut3_.region=1)) 
and (exists (select 1 from ao.ao_activite aoactivite4_ where aoactivite4_.ID_APPEL_OFFRE=appeloffre0_.ID_APPEL_OFFRE and (aoactivite4_.ID_ACTIVITE=1))) 
and appeloffre0_.DATE_OUVERTURE_PLIS>'2015-01-01' 
and (appeloffre0_.CATEGORIE='fournitures' or appeloffre0_.CATEGORIE='travaux' or appeloffre0_.CATEGORIE='services') 
and acheteur1_.ID_ENTITE_MERE=2

解释命令:

+----+--------------------+--------------+------+---------------------------------------------+--------------------+---------+--------------------------------+-------+--------------------------+
| id | select_type        | table        | type | possible_keys                               | key                | key_len | ref                            | rows  | Extra                    |
+----+--------------------+--------------+------+---------------------------------------------+--------------------+---------+--------------------------------+-------+--------------------------+
|  1 | PRIMARY            | acheteur1_   | ref  | PRIMARY,acheteur_ibfk_1                     | acheteur_ibfk_1    | 5       | const                          |     3 | Using where; Using index |
|  1 | PRIMARY            | appeloffre0_ | ref  | appel_offre_ibfk_2                          | appel_offre_ibfk_2 | 4       | ao.acheteur1_.ID_ACHETEUR      | 31061 | Using where              |
|  4 | DEPENDENT SUBQUERY | aoactivite4_ | ref  | ao_activites_activite_fk,ao_activites_ao_fk | ao_activites_ao_fk | 4       | ao.appeloffre0_.ID_APPEL_OFFRE |     3 | Using where              |
|  3 | DEPENDENT SUBQUERY | lieuexecut3_ | ref  | fk_ao_lieuex,fk_region_lieuex               | fk_ao_lieuex       | 4       | ao.appeloffre0_.ID_APPEL_OFFRE |     1 | Using where              |
|  2 | DEPENDENT SUBQUERY | lot2_        | ref  | FK_LOT_AO                                   | FK_LOT_AO          | 4       | ao.appeloffre0_.ID_APPEL_OFFRE |     5 | Using where              |
+----+--------------------+--------------+------+---------------------------------------------+--------------------+---------+--------------------------------+-------+--------------------------+

索引 acheteur_ibfk_1 是 FK 引用 table ENTITE_MERE 因为我在 where 子句中有 acheteur1_.ID_ENTITE_MERE=2

通过使用 ON condition1 AND condition2 等,您可以在 joins 上设置多个条件

SELECT COUNT(appeloffre0_.ID_APPEL_OFFRE) as size  
FROM  ao.appel_offre appeloffre0_  
JOIN ao.acheteur acheteur1_ ON appeloffre0_.ID_ACHETEUR=acheteur1_.ID_ACHETEUR 
JOIN ao.lot lot2_ ON appeloffre0_.ID_APPEL_OFFRE=lot2_.ID_APPEL_OFFRE AND lot2_.ESTIMATION_COUT>=1 
JOIN ao.lieu_execution lieuexecut3_ ON appeloffre0_.ID_APPEL_OFFRE=lieuexecut3_.ID_APPEL_OFFRE AND lieuexecut3_.ID_ACTIVITE=1
JOIN ao.ao_activite aoactivite4_ ON appeloffre0_.ID_APPEL_OFFRE=aoactivite4_.ID_APPEL_OFFRE AND aoactivite4_.ID_ACTIVITE=1
WHERE appeloffre0_.DATE_OUVERTURE_PLIS>'2015-01-01' 
AND (appeloffre0_.CATEGORIE='fournitures' OR appeloffre0_.CATEGORIE='travaux' OR appeloffre0_.CATEGORIE='services') 
AND acheteur1_.ID_ENTITE_MERE=2;

你可以试试:

select count(aa.ID_APPEL_OFFRE) as size  
from  (
select ID_APPEL_OFFRE, ID_ACHETEUR from ao.appel_offre appeloffre0_  
inner join ao.acheteur acheteur1_ 
on appeloffre0_.ID_ACHETEUR=acheteur1_.ID_ACHETEUR 
where appeloffre0_.DATE_OUVERTURE_PLIS>'2015-01-01' 
and (appeloffre0_.CATEGORIE in ('fournitures','travaux','services')) 
and (acheteur1_.ID_ENTITE_MERE=2)) aa
inner join ao.lot lot2_ on lot2_.ID_APPEL_OFFRE=aa.ID_APPEL_OFFRE
inner join ao.lieu_execution lieuexecut3_ on lieuexecut3_.appel_offre=aa.ID_APPEL_OFFRE
inner join ao.ao_activite aoactivite4_  on aoactivite4_.ID_APPEL_OFFRE=aa.ID_APPEL_OFFRE
where 
aoactivite4_.ID_ACTIVITE=1
and lot2_.ESTIMATION_COUT>=1
and lieuexecut3_.region=1;

但是我没有看到你的表,所以我不能 100% 确定你不会因为连接而得到重复项。

通过确保您的 appeloffre0_.CATEGORIE 和 appeloffre0_.DATE_OUVERTURE_PLIS 上有索引,也可能会找到一些唾手可得的成果。

其他应该有索引的字段是 ao.lot.ID_APPEL_OFFRE、ao.lieu_execution.ID_APPEL_OFFRE 和 ao.ao_activite.ID_APPEL_OFFRE,以及 ao.appel_offre.ID_ACHETEUR(所有连接的字段)。

如果还没有的话,我会在你的表上有以下索引...这些是你查询的覆盖索引,这意味着索引有适用的列来获得你的结果,而不必转到实际的原始数据页面。

table           index
appel_offre     ( DATE_OUVERTURE_PLIS, CATEGORIE, ID_APPEL_OFFRE, ID_ACHETEUR )
lot             ( ID_APPEL_OFFRE, ESTIMATION_COUT )
lieu_execution  ( appel_offre, region )
ao_activite     ( ID_APPEL_OFFRE, ID_ACTIVITE )

仅在单个列上建立索引并不能真正帮助优化您正在寻找的内容。另外,我正在计算 DISTINCT ID_APPEL_OFFRE,以防任何 JOINed 表有超过 1 条记录,它不会为你创建笛卡尔结果计数

select  
      count(distinct AOF.ID_APPEL_OFFRE) as size
   from
      ao.appel_offre AOF
         JOIN ao.acheteur ACH
            on AOF.ID_ACHETEUR = ACH.ID_ACHETEUR 
           and ACH.ID_ENTITE_MERE = 2
         JOIN ao.lot
            ON AOF.ID_APPEL_OFFRE = lot.ID_APPEL_OFFRE
           and lot.ESTIMATION_COUT >= 1
         JOIN ao.lieu_execution EX
            ON AOF.ID_APPEL_OFFRE = EX.appel_offre
            and EX.region = 1
         JOIN ao.ao_activite ACT
            ON AOF.ID_APPEL_OFFRE = ACT.ID_APPEL_OFFRE 
           and ACT.ID_ACTIVITE = 1
   where 
          AOF.DATE_OUVERTURE_PLIS > '2015-01-01'
      and (   AOF.CATEGORIE = 'fournitures' 
           or AOF.CATEGORIE = 'travaux' 
           or AOF.CATEGORIE = 'services') 

就像@FuzzyTree 在他的评论中所说 exists 如果它不是 1:1 关系,它比内部连接更快,因为它一旦找到 1 就会终止,而连接将获得每个匹配的行.

但解决方案是我们添加 in 而不是 exists :

where ( appeloffre0_.ID_APPEL_OFFRE IN (select lot2_.ID_APPEL_OFFRE from ao.lot lot2_  
             where    lot2_.ESTIMATION_COUT>=1)
      ) 

所以查询 运行 比 exists 或 joins 非常快。

select  count(appeloffre0_.ID_APPEL_OFFRE) as size  
from  ao.appel_offre appeloffre0_  
inner join ao.acheteur acheteur1_ 
on appeloffre0_.ID_ACHETEUR=acheteur1_.ID_ACHETEUR 
where 
( appeloffre0_.ID_APPEL_OFFRE IN (select lot2_.ID_APPEL_OFFRE from ao.lot lot2_  where    lot2_.ESTIMATION_COUT>=1)) 
and (appeloffre0_.ID_APPEL_OFFRE IN (select lieuexecut3_.appel_offre from ao.lieu_execution lieuexecut3_  where  lieuexecut3_.region=1)) 
and (appeloffre0_.ID_APPEL_OFFRE IN (select aoactivite4_.ID_APPEL_OFFRE from ao.ao_activite aoactivite4_  where aoactivite4_.ID_ACTIVITE=1 )) 
and appeloffre0_.DATE_OUVERTURE_PLIS>'2015-01-01' 
and (appeloffre0_.CATEGORIE='fournitures' or appeloffre0_.CATEGORIE='travaux' or appeloffre0_.CATEGORIE='services') 
and acheteur1_.ID_ENTITE_MERE=2