简体   繁体   English

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

[英]How to optimize a count SQL query on a big table

I have a big table on mysql (innodb) which contains products assets (13 millions of rows). 我有一个关于mysql(innodb)的大表,它包含产品资产(13百万行)。 Here a little schema of my database : 这里是我的数据库的一个小模式:

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

The *file_item* table is the big table with millions of rows. * file_item *表是包含数百万行的大表。 I try to count products by download types with the following sql query : 我尝试使用以下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;

There are 3 indexes on *file_item* table: * file_item *表上有3个索引:

  • product_family_idx (product_id, family_id) product_family_idx(product_id,family_id)
  • family_idx (family_id) family_idx(family_id)
  • product_idx (product_id) Explain output : 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 |                                 | 
+----+-------------+-------+--------+-----------------------------------+---------+---------+-------------------+----------+---------------------------------+

The query takes more than 1 hour to return the results. 查询需要1个多小时才能返回结果。 Please how I can optimize the query ?! 请问我如何优化查询?

Here is your original query: 这是您的原始查询:

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; 

You will need to make two major changes: 您需要进行两项重大更改:

MAJOR CHANGE # 1 : Refactor the Query 主要变化#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;

MAJOR CHANGE # 2 : Create Indexes That Will Support the Refactored Query 主要变化#2:创建支持重构查询的索引

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

Give it a Try !!! 试试看 !!!

Lets decompose the query into parts: 让我们将查询分解为多个部分:

  1. First, fetch each row of file_item => 13M rows 首先,获取file_item => 13M行的每一行
  2. For each returned row, fetch a row of family matching f.id = p.family_id. 对于每个返回的行,获取一行匹配f.id = p.family_id的族。 => 13M fetches, 13M rows => 13M取,13M行
  3. For each returned row, fetch a row of type matching f.id_type = t.id. 对于每个返回的行,获取一个匹配f.id_type = t.id的类型的行。 => 13M fetches, 13M rows => 13M取,13M行
  4. Group by type.id => 10 rows 按类型分组.id => 10行
  5. Sort by type.name => 10 rows to sort 按type.name => 10行排序

As you can see, your query needs fetch 13M rows from family and 13M rows from type. 如您所见,您的查询需要从family中获取13M行,从类型中获取13M行。

You should start be reducing the number of row fetches needed to execute the query: 您应该开始减少执行查询所需的行提取次数:

Assuming that f.id_type is a non-NULL foreign key, you can change the inner join type t to a left join type t . 假设f.id_type是非NULL外键,您可以将inner join type t f.id_type inner join type t更改为left join type t f.id_type left join type t Then, change group by t.id to group by f.id_type . 然后, group by t.idgroup by f.id_type更改为group by f.id_type

Grouping on the f table instead of the t table and changing the inner join to a left join allows MySQL to execute the group by before fetching rows from t . f表而不是t表进行分组并将内连接更改为左连接允许MySQL在从t获取行之前执行该group by

group by drastically reduces the number of rows, so this drastically reduce the number of fetches from t too: group by大幅减少行数,因此大大减少了t的提取次数:

  1. First, fetch each row of file_item => 13M rows 首先,获取file_item => 13M行的每一行
  2. For each returned row, fetch a row of family matching f.id = p.family_id. 对于每个返回的行,获取一行匹配f.id = p.family_id的族。 => 13M fetches, 13M rows => 13M取,13M行
  3. Group by type.id => 10 rows 按类型分组.id => 10行
  4. For each returned row, fetch a row of type matching f.id_type = t.id. 对于每个返回的行,获取一个匹配f.id_type = t.id的类型的行。 => 10 fetches , 10 rows => 10次​​提取 ,10行
  5. Sort by type.name => 10 rows to sort 按type.name => 10行排序

The result is that the query already fetches 13M less rows. 结果是查询已经减少了13M行。

You can reduce that even more by denormalizing the schema a little: 您可以通过对模式进行非规范化来减少更多:

If you add a family_type_id column in file_item, you could rewrite your query like this: 如果在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

With an index on file_item.family_type_id, this query should execute in milliseconds. 使用file_item.family_type_id上​​的索引,此查询应以毫秒为单位执行。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM