如何在大表上优化计数SQL查询

时间:2011-08-24 16:19:14

标签: mysql sql query-optimization

我有一个关于mysql(innodb)的大表,其中包含产品资产(13百万行)。这是我的数据库的一个小模式:

product <-many2one-- file_item --one2many--> family --many2one--> download_type

* file_item *表是包含数百万行的大表。我尝试使用以下sql查询按下载类型计算产品:

select t.name as type, 
count(p.product_id) as n 
from file_item p 
inner join family f on f.id = p.family_id 
inner join type t on f.id_type = t.id 
group by t.id order by t.name;

* file_item * table上有3个索引:

  • product_family_idx(product_id,family_id)
  • family_idx(family_id)
  • product_idx(product_id) 解释输出:
+----+-------------+-------+--------+-----------------------------------+---------+---------+-------------------+----------+---------------------------------+
| id | select_type | table | type   | possible_keys                     | key     | key_len | ref               | rows     | Extra                           |
+----+-------------+-------+--------+-----------------------------------+---------+---------+-------------------+----------+---------------------------------+
|  1 | SIMPLE      | p     | ALL    | FAMILY_IDX,PRODUCT_FAMILY_IDX     | NULL    | NULL    | NULL              | 13862870 | Using temporary; Using filesort | 
|  1 | SIMPLE      | f     | eq_ref | PRIMARY,TYPE_ID                   | PRIMARY | 4       | MEDIA.p.FAMILY_IDX|        1 |                                 | 
|  1 | SIMPLE      | t     | eq_ref | PRIMARY                           | PRIMARY | 4       | MEDIA.f.TYPE_ID   |        1 |                                 | 
+----+-------------+-------+--------+-----------------------------------+---------+---------+-------------------+----------+---------------------------------+

查询需要1个多小时才能返回结果。 请问我如何优化查询?!

2 个答案:

答案 0 :(得分:5)

这是您的原始查询:

select t.name as type,  
count(p.product_id) as n  
from file_item p  
inner join family f on f.id = p.family_id  
inner join type t on f.id_type = t.id  
group by t.id order by t.name; 

您需要进行两项重大更改:

MAJOR CHANGE#1:重构查询

SELECT A.ProductCount,B.name type
FROM
(
    SELECT id_type id,COUNT(1) ProductCount
    FROM
    (
        SELECT p.id_type
        FROM (SELECT family_id,id_type FROM file_item) p
        INNER JOIN (SELECT id FROM family) f on f.id = p.family_id
    ) AA
    GROUP BY id_type
) A
INNER JOIN type B USING (id)
ORDER BY B.name;

重大变更#2:创建支持重构查询的索引

ALTER TABLE file_item ADD INDEX family_type_idx (family_id,id_type);

试一试!!!

答案 1 :(得分:1)

让我们将查询分解为部分:

  1. 首先,获取file_item =&gt;的每一行13M行
  2. 对于每个返回的行,获取一行匹配f.id = p.family_id的族。 =&GT; 13M取,13M行
  3. 对于每个返回的行,获取一个匹配f.id_type = t.id的类型的行。 =&GT; 13M取,13M行
  4. 按类别分组.id =&gt; 10行
  5. 按type.name =&gt;排序10行排序
  6. 如您所见,您的查询需要从family中获取13M行,从类型中获取13M行。

    您应该开始减少执行查询所需的行提取次数:

    假设f.id_type是非NULL外键,您可以将inner join type t更改为left join type t。然后,将group by t.id更改为group by f.id_type

    f表而不是t表进行分组并将内连接更改为左连接允许MySQL在从group by获取行之前执行t

    group by大幅减少了行数,因此大大减少了t的提取次数:

    1. 首先,获取file_item =&gt;的每一行13M行
    2. 对于每个返回的行,获取一行匹配f.id = p.family_id的族。 =&GT; 13M取,13M行
    3. 按类别分组.id =&gt; 10行
    4. 对于每个返回的行,获取一个匹配f.id_type = t.id的类型的行。 =&GT; 10次提取,10行
    5. 按type.name =&gt;排序10行排序
    6. 结果是查询已经少了13M的行。

      你可以通过对模式进行非规范化来减少更多:

      如果在file_item中添加family_type_id列,则可以像这样重写查询:

      SELECT count(1)
      FROM file_item p
      JOIN type t ON t.id = p.family_type_id
      GROUP BY p.family_type_id
      ORDER BY t.name
      

      对于file_item.family_type_id的索引,此查询应该以毫秒为单位执行。