mysql - 两次连接表会使查询变慢

标签 mysql join

我的问题是,在同一张表上两次使用 JOIN 时,我的查询速度非常慢。

我想检索给定类别中的所有产品。但是由于产品可以属于多个类别,我还想获得应该提供 URL 基础的 (c.canonical) 类别。因此,我在 categories AS ccategories_products AS cp2 上有 2 个额外的 JOIN

原始查询

SELECT p.product_id
FROM products AS p
JOIN categories_products AS cp
    ON p.product_id = cp.product_id
JOIN product_variants AS pv
    ON pv.product_id = p.product_id
WHERE cp.category_id = 2
    AND p.status = 2
GROUP BY p.product_id
ORDER BY cp.product_sortorder ASC
LIMIT 0, 40

解释

| id | select_type | table |   type |          possible_keys |                    key | key_len |                     ref | rows |                                        extra |
|----|-------------|-------|--------|------------------------|------------------------|---------|-------------------------|------|----------------------------------------------|
|  1 |      SIMPLE |    cp |    ref | FK_categories_products | FK_categories_products |       4 |                   const | 1074 | Using where; Using temporary; Using filesort |
|  1 |      SIMPLE |     p | eq_ref |                PRIMARY |                PRIMARY |       4 | superlove.cp.product_id |    1 |                                    Using where |
|  1 |      SIMPLE |    pv |    ref |    FK_product_variants |    FK_product_variants |       4 |  superlove.p.product_id |    1 |                                    Using where |    

慢查询

SELECT p.product_id, c.category_id
FROM products AS p
JOIN categories_products AS cp
    ON p.product_id = cp.product_id
JOIN categories_products AS cp2        // Extra line
    ON p.product_id = cp2.product_id   // Extra line
JOIN categories AS c                   // Extra line
    ON cp2.category_id = c.category_id // Extra line
JOIN product_variants AS pv
    ON pv.product_id = p.product_id
WHERE cp.category_id = 2
    AND p.status = 2
    AND c.canonical = 1                // Extra line
GROUP BY p.product_id
ORDER BY cp.product_sortorder ASC
LIMIT 0, 40

解释

| id | select_type | table |   type |          possible_keys |                    key | key_len |                      ref | rows |                                        extra |
|----|-------------|-------|--------|------------------------|------------------------|---------|--------------------------|------|----------------------------------------------|
|  1 |      SIMPLE |     c |    ALL |                PRIMARY |                 (null) |  (null) |                   (null) |  221 | Using where; Using temporary; Using filesort |
|  1 |      SIMPLE |   cp2 |    ref | FK_categories_products | FK_categories_products |       4 |  superlove.c.category_id |   33 |                                              |
|  1 |      SIMPLE |     p | eq_ref |                PRIMARY |                PRIMARY |       4 | superlove.cp2.product_id |    1 |                                  Using where |
|  1 |      SIMPLE |    pv |    ref |    FK_product_variants |    FK_product_variants |       4 |   superlove.p.product_id |    1 |                                  Using where |
|  1 |      SIMPLE |    cp |    ref | FK_categories_products | FK_categories_products |       4 |                    const | 1074 |                                  Using where |

最佳答案

MySQL 优化器似乎无法处理此查询。我的印象是只有很少的产品会属于所请求的类别,但可能会有很多规范类别。但是,优化器显然无法判断 cp.category_id = 2 是比 c.canonical = 1 更强的条件,因此它以 c< 开始新查询 而不是 cp,导致沿途有很多多余的行。

向优化器提供数据

您的第一次尝试应该是尝试向优化器提供所需的数据:使用 ANALYZE TABLE命令,您可以收集有关 key 分发的信息。为此,您必须准备好合适的 key 。所以也许您应该在 categories.canonical 上添加一个键。然后 MySQL 会知道(如果我理解正确的话)该列只有两个不同的值,甚至可能知道每个值有多少行。幸运的话,这会告诉它使用 c.canonical = 1 作为起点将是一个糟糕的选择。

强制加入顺序

如果这没有帮助,那么我建议您使用 STRAIGHT_JOIN 强制执行订单.特别是,您可能希望将 cp 强制作为第一个表,就像您的原始(和快速)查询那样。如果这解决了问题,您可以坚持使用该解决方案。如果不是,那么您应该提供一个新的 EXPLAIN 输出,以便我们可以看到该方法失败的地方。

架构注意事项

还有一件事需要考虑:您的问题意味着对于每一种产品,都有一个与之关联的规范类别。但是您的数据库模式并没有反射(reflect)出这一事实。您可能需要考虑修改架构以反射(reflect)该事实的方法。例如,您可以在 products 表中有一个名为 canonical_category_id 的列,并将 categories_products 仅用于非规范类别。如果您使用这样的设置,您可能需要 create a VIEW使用 UNION 将产品加入所有它们的类别,包括规范和非规范类别像这样:

CREATE VIEW products_all_categories AS
SELECT product_id, canonical_category_id AS category_id
FROM products
UNION ALL
SELECT product_id, category_id
FROM categories_products

在您不关心类别是否规范的地方,您可以使用它代替 categories_products。您甚至可以重命名表并将 View 命名为 categories_products,这样您现有的查询就可以像以前一样工作。您应该在该查询中使用的 products 的两列上添加索引。甚至可能有两个索引,一个用于这些列的任一顺序。

不确定整个设置是否适合您的应用程序。不确定它是否真的会带来预期的速度增益。最后,您可能被迫维护冗余数据,例如 products.canonical 列,以及对 categories_products 表中规范类别的引用。我知道从设计的角度来看冗余数据是丑陋的,但为了性能起见,为了避免长时间的计算,它可能是必要的。至少在不支持 materialized views 的 RDBMS 上.您可能会使用触发器来保持数据的一致性,尽管我在这方面没有实际经验。

关于mysql - 两次连接表会使查询变慢,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/19543837/

相关文章:

MySQL View 连接多行表

mysql删除重复评论?

mysql - MySQL 中使用 DATE() 函数比较两个日期的差异

sql - 连接同一个表中的两个选择查询

sql - 在嵌套循环连接中哪个表被认为是 'inner'

python-2.7 - 如何从 Quandl 加入/合并两个数据框?

mysql - 如何在 mysql 中创建模式以及如何将现有记录导入数据库?

php - raquo符号整理mysql插入中的字符串

javascript - 将 mysql 查询行存储在变量中以供以后在另一个 mysql 查询中使用

mysql right join where 在一个子句上返回空