如何使用许多 JOIN 提高查询性能

Posted

技术标签:

【中文标题】如何使用许多 JOIN 提高查询性能【英文标题】:How to Improve Query Performance with many JOINs 【发布时间】:2014-01-21 12:28:10 【问题描述】:

我有一个查询(目的是制作视图),它使用几个连接来获取每一列。对于添加的每组连接,性能会迅速下降(指数级?)。

加快查询速度的好方法是什么?请查看查询中的 cmets。

如果有帮助,这是使用 WordPress 数据库架构。

这是 EXPLAIN 的截图

产品表

+--+----+
|id|name|
+--+----+
|1 |test|
+--+----+

元数据表

+----------+--------+-----+
|product_id|meta_key|value|
+----------+--------+-----+
|1         |price   |9.99 |
+----------+--------+-----+
|1         |sku     |ABC  |
+----------+--------+-----+

TERM_RELATIONSHIPS 表

+---------+----------------+
|object_id|term_taxonomy_id|
+---------+----------------+
|1        |1               |
+---------+----------------+
|1        |2               |
+---------+----------------+

TERM_TAXONOMY 表

+----------------+-------+--------+
|term_taxonomy_id|term_id|taxonomy|
+----------------+-------+--------+
|1               |1      |size    |
+----------------+-------+--------+
|2               |2      |stock   |
+----------------+-------+--------+

条款表

+-------+-----+
|term_id|name |
+-------+-----+
|1      |500mg|
+-------+-----+
|2      |10   |
+-------+-----+

查询

SELECT 
  products.id,
  products.name,
  price.value AS price,
  sku.value AS sku,
  size.name AS size
FROM products

/* These joins are performing quickly */

INNER JOIN `metadata` AS price ON products.id = price.product_id AND price.meta_key = 'price'
INNER JOIN `metadata` AS sku ON products.id = sku.product_id AND sku.meta_key = 'sku'

/* Here's the part that is really slowing it down - I run this chunk about 5 times with different strings to match */

INNER JOIN `term_relationships` AS tr ON products.id = tr.object_id
  INNER JOIN `term_taxonomy` AS tt
  ON tr.term_taxonomy_id = tt.term_taxonomy_id AND tt.taxonomy = 'size'
    INNER JOIN `terms` AS size
    ON tt.term_id = size.term_id

【问题讨论】:

OR 可以杀死索引使用,尝试使用联合重写。 显示每个表的DESC tableName 的输出。 或至少输出EXPLAIN <your SQL>; @Manu 我在问题中添加了 EXPLAIN 结果的图像。谢谢。 解释肯定是不同的查询!表 tt2、tt3 不存在。 -1,直到你修复。 【参考方案1】:

我会建议那些:

考虑从业务级别减少这些联接; 如果不能从“顶层”(业务级别)做,并且数据不是实时的,我建议准备一个内存表(我知道解决方案并不理想)。并直接从内存表中选择您的数据。

根据我的经验:

“joins”是性能杀手,你的数据越大,越痛苦; 尽量摆脱联接,除非必须,否则不要试图通过保留联接来提高查询性能。通常我会尝试从“顶部”到“底部”解决这些问题 最后一个建议是如果以上所有方法都不起作用。如果值得的话,我会考虑“map/reduce + 全文搜索”。

(请原谅我没有提供提高您查询性能的解决方案。)

【讨论】:

【参考方案2】:

METADATA_TABLE 和 TERM_RELATIONSHIP_TABLE 没有任何主键。当这些表中有大量记录时,您的查询性能将受到影响。

检查点可提高您的绩效。

    所有表都应该有主键。这是因为表格中的行将进行物理排序。 对于涉及少数表的小型查询,在表中保留主键就足够了。 如果您仍希望提高性能,请为诸如 term_relationships table* 的 *object_Id 字段之类的列创建非聚集索引。应该为表中参与连接操作的列创建非聚集索引。

但是,需要注意的是,在发生多次插入和更新的表上,非聚集索引应该非常少。 这不是一个简单的问题,不能仅根据运行时间来回答。还有其他因素会影响答案,尤其是在运行存储过程的环境具有大量事务性的情况下。

你可以找到更多here

【讨论】:

所有表都有一个主键,每一列都被我加入的索引。【参考方案3】:

下面的脚本是根据 SQL Server 规则格式化的 - 你可以根据 mysql 规则更改它并试一试 -

SELECT 
  P.id,
  P.name,
  PIVOT_METADATA.price,
  PIVOT_METADATA.sku,
  size.name AS size
FROM products P (NOLOCK)

INNER JOIN term_relationships AS tr (NOLOCK)
    ON P.id = tr.object_id

INNER JOIN term_taxonomy AS tt (NOLOCK)
    ON tr.term_taxonomy_id = tt.term_taxonomy_id AND tt.taxonomy = 'size'

INNER JOIN terms AS size (NOLOCK)
    ON tt.term_id = size.term_id

INNER JOIN METADATA (NOLOCK)
    PIVOT
    (
        MAX(value)
        FOR [meta_key] IN (price,sku)
    )AS PIVOT_METADATA
    ON P.id = PIVOT_METADATA.product_id

我认为您的查询可能是瓶颈 - 您加入元数据 2 次。由于您的表中存在一对多关系,因此元数据 2-join 不会受到伤害,但之后随着您加入更多表 - 由于一对多关系增加的行数 - 因此性能下降.

我努力实现的目标 - 我确保尽可能多地实现一对一关系。为此,我做了一个 Pivot on Metadata 并将价格和 sku 作为列。现在我的产品 ID 在元数据数据透视表中应该只有一行。另外,我已经确保我在最后加入了这个 picot。

试一试。请通过我的回答分享预期性能、您拥有的记录数量以及您获得的性能。

【讨论】:

谢谢,我想试试这个,但是 MySQL 中没有 PIVOT,所以我不知道如何适应这个。【参考方案4】:

确保所有存在“ON”条件语句的列都应该被索引。 这将显着提高速度。

【讨论】:

【参考方案5】:

您的性能问题很可能是由与“term_taxonomy”表的联接引起的。 所有其他联接似乎都使用主键(您可能在其中有工作索引)。 所以我的建议是在 term_taxonomy_idterm_id 上添加一个复合索引(或者如果必须:taxonomy)。像这样:

CREATE UNIQUE INDEX idx_term_taxonomy_id_taxonomy
ON term_taxonomy( term_taxonomy_id, taxonomy);

希望这会对你有所帮助。

【讨论】:

这可行。 +1。此外,为metadata (product_id, mate_key) 创建一个复合 索引也是一个好主意,因为具有广泛的索引多样性意味着mysql 优化器有更多选项来创建更好的执行计划。 宾果游戏!在 product_id 和 meta_key 的元数据表中创建复合索引使查询在 1 秒内执行,与 30 多秒相比有了巨大的改进! @Karolis 您可以单独发布您的解决方案,以便我可以给您赏金吗?还要感谢 carleson 提供解决方案的提示。 @dloewen 好吧,我认为 carleson 给了你正确的方向,我的评论只是 2 美分,以使他的答案更全面:-)【参考方案6】:

试试这个:

SELECT p.id, p.name, MAX(CASE m.meta_key WHEN 'price' THEN m.value ELSE '' END) AS price, 
       MAX(CASE m.meta_key WHEN 'sku' THEN m.value ELSE '' END) AS sku, s.name AS size
FROM products p 
INNER JOIN `metadata` AS m ON p.id = m.product_id  
INNER JOIN `term_relationships` AS tr ON p.id = tr.object_id 
INNER JOIN `term_taxonomy` AS tt ON tr.term_taxonomy_id = tt.term_taxonomy_id AND tt.taxonomy = 'size'
INNER JOIN `terms` AS s ON tt.term_id = s.term_id
GROUP BY p.id;

如果您仍然发现查询速度很慢,请添加我的查询的 EXPLAIN 计划,以便我可以找到需要 INDEX 的列。

【讨论】:

注意 CASE 符合 SQL 标准。【参考方案7】:
    Declare @query as NVARCHAR(MAX)
    set @query = ('SELECT 
    products.id,
    products.name,
    price.value AS price,
    sku.value AS sku,
    size.name AS size
    FROM products
    INNER JOIN metadata AS price ON products.id = price.product_id AND price.meta_key = price
    INNER JOIN metadata AS sku ON products.id = sku.product_id AND sku.meta_key = sku
    INNER JOIN term_relationships AS tr ON products.id = tr.object_id
    INNER JOIN term_taxonomy AS tt
    ON tr.term_taxonomy_id = tt.term_taxonomy_id AND tt.taxonomy = size
    INNER JOIN terms AS size
    ON tt.term_id = size.term_id
    into #t')

    exec(@query);
    select * from #t

我希望上述方式会减少时间利用率,或者创建一个包含您选择的所有字段的临时表并通过将临时表连接到所有其他表来更新临时表也可能有效,我不是确定,但即使我也在等待你的结果,因为你的问题似乎很有趣

【讨论】:

我看不出这样做的意义?在这种情况下,性能会变得更糟,因为您有相同的查询,然后还使用临时表。如果您将大查询分成较小的子集,临时表可以加快慢查询,但这里不是这种情况。

以上是关于如何使用许多 JOIN 提高查询性能的主要内容,如果未能解决你的问题,请参考以下文章

从 Android ContactsContract.Contacts 查询所有联系人的许多数据时如何提高性能?

如何在一个查询中通过 LINQ Include 检索子数据以提高性能?

使用子查询时如何提高查询性能

提高分层 SQL 结构的性能

提高 self-JOIN SQL Query 性能

如何提高风数据SQL查询性能