【问题标题】:Optimize MySQL COUNT / group by query: show only categories that have products associated优化 MySQL COUNT / group by query:仅显示与产品关联的类别
【发布时间】:2016-02-22 02:54:24
【问题描述】:
select COUNT(p.id) AS `num`, cat.id, cat.name, cat.parent_id AS `parent_id` 
from products p 
INNER JOIN `products_categories` AS `pc` ON p.id=pc.products_id 
INNER JOIN `categories` AS `cat` ON pc.categories_id=cat.id 
WHERE p.status = 1 AND p.gender IN ('female','neutral') 
group by cat.id

解释查询:

1   SIMPLE  p   ref PRIMARY,gender,status   status  1   const   139107  Using where; Using temporary; Using filesort
1   SIMPLE  pc  ref products_id,categories  products_id 4   mydb.p.id   1   Using index
1   SIMPLE  cat eq_ref  PRIMARY,categoryname    PRIMARY 4   mydb.pc.categories_id   1   Using where

相关索引:

products    0   PRIMARY 1   id  A   299339              BTREE       
products    1   title   1   title   A   299339              BTREE       
products    1   sku 1   sku A   299339              BTREE       
products    1   body    1   body    A   299339  200         BTREE       
products    1   short_description   1   short_description   A   299339  200     YES BTREE       
products    1   keywords    1   keywords    A   2   200         BTREE       
products    1   gender  1   gender  A   10              BTREE       
products    1   status  1   status  A   2               BTREE       
products    1   brand_id    1   brand_id    A   3741            YES BTREE       
products    1   merchant    1   merchant_id A   52              BTREE       
products    1   title_2 1   title,body,keywords     299339              FULLTEXT        
products    1   title_3 1   title       299339              FULLTEXT        
products    1   body_2  1   body        299339              FULLTEXT        

products_categories 0   PRIMARY 1   id  A   514054              BTREE       
products_categories 1   products_id 1   products_id, categories_id  A   514054              BTREE           
products_categories 1   categories  1   categories_id   A   266             BTREE       

categories  0   PRIMARY 1   id  A   154             BTREE       
categories  1   categoryname    1   name    A   154             BTREE       

这是一个包含产品、类别以及它们之间的 N:N 关系的数据库。产品可以属于 1 个或多个类别。

我基本上需要一个查询来告诉我,对于我拥有的当前产品过滤器(在这种情况下是状态和性别),该类别是否有任何产品(因此我可以隐藏没有产品的类别)。目前我统计了每个类别中的产品来了解这一点。

查询的 WHERE 参数将根据用户选择的过滤器而变化,因此该部分在此优化中不是很重要。

我不需要一个类别的确切产品数量,只要他们有产品与否。 Products 表有很多索引,有 products_categories 和 categories 表。 Products 表有大约 400k 个产品,150 个类别和 500k 个 products_categories。

AWS RDS 上托管的 MySQL 5.6.22,InnoDB 中的所有表。

我知道我的解释查询显示了为什么这很慢(通过很多产品),但我不知道如何优化它......也许可以换一种方式来思考这个问题?

【问题讨论】:

    标签: mysql sql sql-optimization


    【解决方案1】:

    对于这个查询:

    select COUNT(p.id) AS `num`, cat.id, cat.name, cat.parent_id AS `parent_id` 
    from products p INNER JOIN
         products_categories `pc`
         ON p.id = pc.products_id INNER JOIN
         categories cat
          ON pc.categories_id = cat.id 
    WHERE p.status = 1 AND p.gender IN ('female', 'neutral') 
    group by cat.id;
    

    您需要所有 join 键的索引。我会推荐products(status, gender, id)products_categories(products_id, categories_id)categories(id)

    有时,在 MySQL 中,使用相关子查询比使用 group by 更快:

    select c.*,
           (select count(*)
            from products_categories `pc` INNER JOIN
                 products p
                 ON p.id = pc.products_id
            where pc.categories_id = cat.id AND
                  p.status = 1 AND p.gender IN ('female', 'neutral') 
           ) as cnt
    from categories c;
    

    此版本需要products_categories(categories_id, products_id)products(id, status, gender) 上的索引。

    【讨论】:

    • 我已多次关注您的回答。我已经看到您在答案中建议了索引以获得更好的性能。如果您为特定表创建了太多索引,这将影响插入、更新和删除操作期间的性能。如何创建有效的索引?
    • @RGS 。 . .是的,索引会影响数据修改操作的性能。这是否重要取决于数据修改对查询的相对重要性。
    • 当我将运行缓慢的查询的跟踪文件放入 SQL Query Tuning Advisor 时,它提示索引过多。我们是否可以盲目地创建那些缺失的索引?
    【解决方案2】:

    您的查询返回 139107 条匹配记录,因为您使用的过滤条件不是很严格(状态 = 1,性别 = 女性或中性)。试试这个

    SELECT cat.id, cat.name, cat.parent_id AS `parent_id`,
          COUNT(p.id) AS `num` 
    FROM `categories` AS `cat`
    INNER JOIN `products_categories` AS `pc` ON pc.categories_id=cat.id 
    INNER JOIN products AS p ON p.id=pc.products_id 
    WHERE p.status = 1 AND p.gender IN ('female','neutral') 
    GROUP BY cat.id
    HAVING COUNT(p.id)>0
    

    添加HAVING 不会自动改进查询。问题是您的过滤条件返回许多匹配的产品。按性别或布尔状态(true/false)过滤记录可能会因为许多重复值而导致发生表扫描,即使状态和性别是索引,MySQL 仍然可能认为运行表扫描比使用索引更便宜。

    HAVING 用于过滤任何没有产品的类别。试试这个

    SELECT cat.id, cat.name, cat.parent_id AS `parent_id`,
          COUNT(pc._products_id) AS `num` 
    FROM `categories` AS `cat`
    INNER JOIN `products_categories` AS `pc` ON pc.categories_id=cat.id 
    GROUP BY cat.id
    HAVING COUNT(pc.products_id)>0
    

    以上查询不会与产品表连接。它只查看 product_categories 是否与产品相关联。

    【讨论】:

    • 一个非常快速的测试似乎添加 HAVING 并不能提高性能,实际上运行起来似乎有点慢
    • 您的第二个建议非常快,但我需要加入产品表,这样我才能过滤状态 = 1 和性别的产品,这是我的查询要求:(
    • COUNT() 在 InnoDB 上已经很慢了。如果您不尽可能多地过滤记录,情况会变得更糟。我的建议使用第二个查询的输出,你说的精确值不会有问题。或者您可以在 product_categories 表中添加状态和性别。这将导致产品表中的冗余数据,但您无需加入产品表。要加入的表更少。
    猜你喜欢
    • 1970-01-01
    • 2016-08-14
    • 1970-01-01
    • 2012-10-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-03-18
    相关资源
    最近更新 更多