2011-08-24 130 views
3

我有一个包含产品资产(13百万行)的mysql(innodb)的大表。这里是我的数据库的一个小模式:如何优化大表上的计数SQL查询

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

* file_item * table是一个包含数百万行的大表。我试着以计算下载类型的产品,下面的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 3个指标*表:

  • product_family_idx(产品,family_id)
  • family_idx(family_id)
  • product_idx(产品) 解释输出:
 
+----+-------------+-------+--------+-----------------------------------+---------+---------+-------------------+----------+---------------------------------+ 
| 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

表格索引是否正确?请提供具有索引和该查询的EXPLAIN的模式。谢谢! – Wiseguy

+1

请给我们输出“解释;” –

+0

@Wiseguy当然,我在文本中添加了索引。 – juliusdev

回答

2

允许分解查询分成部分:

  1. 首先,取file_item的每一行=> 13M行
  2. 对于每个返回的行,取家族匹配f.id = p.family_id的一排。 => 13M提取,13M行
  3. 对于每个返回的行,获取一行匹配f.id_type = t.id的类型。 => 13M取,13M行
  4. 集团通过type.id => 10行
  5. 排序type.name => 10行进行排序

正如你所看到的,您的查询需要读取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表,并改变内加入一个LEFT JOIN允许的MySQL从t提取行之前执行的group by

group by大大减少的行数,所以这极大地减少取的数量从t太:

  1. 首先,取file_item的每一行=> 13M行
  2. 对于每个返回的行,取一排匹配f.id = p.family_id的家庭。 => 13M提取,13M行
  3. 按类型分组.id => 10行
  4. 对于每个返回的行,获取一行匹配f.id_type = t.id的类型。=>10提取,10行
  5. 排序type.name => 10行进行排序

其结果是,该查询已经获取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,这个查询应该以毫秒为单位执行。

4

这里是原始查询:

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; 

您将需要进行两个重大变化:

重大变化#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); 

试试看!

+0

你能解释为什么这个重构? – arnaud576875

+0

在您的查询中,GROUP BY和ORDER BY子句在所有连接之后进行评估。诀窍是做两件事:1)强制查询只使用所需的键具有较小的临时表,2)最后完成JOIN。我从这段视频中了解到了这一点:http://youtu.be/ZVisY-fEoMw。我使用这种技术来解答另一个非常复杂的问题,涉及在StackOverflow中的行:thousansd:http://stackoverflow.com/questions/5983156/fetching-a-single-row-from-join-table/6023217#6023217 – RolandoMySQLDBA