我的问题是,在同一个桌面上使用JOIN
两次时,我的查询速度很慢。
我想从特定类别中检索所有产品。但由于产品可以分为多个类别,我还希望获得应该提供URL基础的(c.canonical
)类别。因此,我在JOIN
和categories AS c
上增加了2 categories_products AS cp2
。
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 |
答案 0 :(得分:1)
MySQL优化器似乎在查询时遇到问题。我的印象是,只有极少数产品会出现在所请求的类别中,但可能会有许多规范类别。但是,优化器显然无法判断cp.category_id = 2
是否比c.canonical = 1
更强大,因此它使用c
而不是cp
启动新查询,从而导致大量多余的问题一路上的行。
您的第一次尝试应该是尝试为优化程序提供所需的数据:使用ANALYZE TABLE
命令,您可以收集有关密钥分发的信息。为此,您必须有合适的钥匙。所以也许你应该在categories.canonical
上添加一个键。然后,MySQL会知道(如果我理解正确的话)该列只有两个不同的值,甚至可能有多少行。运气好的话,这会告诉它使用c.canonical = 1
作为起点将是一个糟糕的选择。
如果这没有帮助,那么我建议您使用STRAIGHT_JOIN
强制执行订单。特别是,您可能希望强制cp
作为第一个表,就像您的原始(和快速)查询一样。如果这样可以解决问题,那么您可以坚持使用该解决方案。如果没有,那么您应该提供新的EXPLAIN
输出,以便我们可以看到该方法失败的位置。
还有一件事需要考虑:您的问题意味着对于每个产品,只有一个与之相关的规范类别。但是您的数据库架构并未反映这一事实。您可能想要考虑修改模式以反映该事实的方法。例如,您可以在canonical_category_id
表中使用名为products
的列,并仅将categories_products
用于非规范类别。如果你使用这样的设置,你可能希望create a VIEW
将产品加入所有他们的类别,包括规范和非规范类别,使用UNION
这样:< / p>
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
。您甚至可以重命名该表并将视图命名为categories_products
,以便您现有的查询可以像以前一样工作。您应该在此查询中使用的products
的两列上添加索引。甚至可能是两个索引,一个用于这些列的任何顺序。
不确定整个设置是否可以在您的应用程序中使用。不确定它是否真的会带来预期的速度增益。最后,您可能会被迫维护冗余数据,例如products.canonical
列,以及categories_products
表中对规范类别的引用。我知道从设计的角度来看冗余数据是丑陋的,但为了性能,可能需要避免长时间的计算。至少在不支持materialized views的RDBMS上。您可以使用触发器来保持数据的一致性,但我没有实际经验。