【问题标题】:How to Speed Up Simple Join如何加快简单加入
【发布时间】:2024-05-21 11:50:02
【问题描述】:

我不擅长 SQL。

我正在寻找一种方法来加快这样的简单连接:

SELECT
    E.expressionID,
    A.attributeName,
    A.attributeValue
FROM 
    attributes A
JOIN
    expressions E
ON 
    E.attributeId = A.attributeId

我这样做了几十万次,随着桌子变大,它花费的时间越来越多。

我正在考虑索引 - 如果我要加快对单个表的选择,我可能会在表达式表的 expressionID 上放置非聚集索引,在属性表的 (attributeName, attributeValue) 上放置另一个索引 - 但我没有知道这如何适用于联接。

编辑:我已经在表达式表上的 expressionId (PK)、attributeId (PK, FK) 和属性表上的 attributeId (PK) 上拥有一个聚集索引

我已经看到this 的问题,但我要求的是更一般的问题,可能更简单。

任何帮助表示赞赏!

【问题讨论】:

  • 这个查询中有 WHERE 子句吗?返回了多少行?
  • no WHERE - 我正在使用它来填充一个临时表,然后根据一组名称-值对使用许多 where 子句进行过滤以获得匹配的 expressionId
  • 您应该发布查询的后半部分。我敢打赌,速度变慢是由于将大量记录插入到该临时表中造成的。通过组合它们可能会大大改善它,因此更早使用 WHERE 子句来防止巨大的插入。
  • 开始了另一个问题,因为这个问题更通用并且面向简单的连接性能 --> *.com/questions/923136/…

标签: sql-server performance join


【解决方案1】:

您肯定希望在attributesexpressions 表上都有attributeID 的索引。如果您目前没有这些索引,我认为您会看到很大的加速。

【讨论】:

  • 不要忘记两列应该是相同的数据类型,如果它们是字符数据,则应该是相同的排序规则。
  • 知道主键会有所帮助。作为主键的单个列将已被索引。您的表达式表可能有两个构成主键的字段。这意味着在 E.attributeId 上创建索引将是可行的方法。主键将使用 E.ID 和 E.attributeId 创建一个索引。仅为 E.attributeId 添加索引会加快速度。
  • 其实主键并不是所有平台都自动索引的。例如 MySql 默认不会在主键上创建索引。
  • 我在表达式表上有一个关于 expressionId、attributeId (PK) 的索引,在属性表上有一个关于 attributeId (PK) 的聚集索引
  • 两个表不一定都需要索引。以这种方式盲目地添加索引实际上是一种不好的形式。您需要确保您的数据库统计数据是最新的,并查看表大小如何叠加。无论如何,优化器很可能会对基表进行全表扫描(因为没有 WHERE 子句),因此基表上 AttributeId 上的索引只是浪费空间。
【解决方案2】:

事实上,因为返回的列太少,我会考虑为这个查询使用覆盖索引

即包含查询中所有字段的索引。

【讨论】:

  • 我认为 Goblyn 建议在 A.attributeId、A.attributeName、A.attributeValue 和 E.attributeId 和 E.expressionID 上添加一个索引......但我不是 100% 确定.这样做的理论是查询的所有数据将直接来自索引并且永远不会命中表。
  • 对不起,我不清楚。格雷格是正确的。在这种情况下,将有两个覆盖索引,每个表一个,连接将发生在两个覆盖索引之间,而不涉及实际表。
  • 我会试一试并报告
【解决方案3】:

您需要关心的一些事情是索引、查询计划和统计信息。

在attributeId 上放置索引。或者,确保在attributeId 是键中的第一列的位置存在索引(如果它不是第一列,SQL Server 仍然可以使用索引,但它没有那么快)。

在查询分析器中突出显示查询并点击 ^L 查看计划。您可以看到表是如何连接在一起的。 几乎总是,使用索引总比不使用要好(在某些边缘情况下,如果表足够小,索引可能会减慢您的速度 - 但现在,请注意 99% 的时间索引很好)。

注意表连接的顺序。 SQL Server 维护有关表大小的统计信息,并将确定首先加入哪个更好。对内部 SQL Server 程序进行一些调查以更新统计信息 - 时间太长,所以我手头没有这些信息。

这应该让你开始。真的,即使是这样一个简单的查询,也可以写一整章来说明数据库如何优化。

【讨论】:

    【解决方案4】:

    我敢打赌,您的问题是要插入到该临时表中的大量行。有什么方法可以在数据库中的每一行 SELECT 之前添加一个 WHERE 子句?

    【讨论】:

    • 我想我可以在这个查询中使用属性名+属性值上的 OR 分离来加快它的速度,但问题是我必须动态附加 OR 分离,因为我需要像 WHERE ( attributeName = 'X' AND attributeValue = 'Y') OR (attributeName = 'Z' AND attributeValue = 'W') ... 最终获得一组给定名称-值对的 ExpressionId。所以我可能会浪费时间循环使用名称-值对的表并为 WHERE 子句构建这些 OR 分离。
    • 那可能会更好?或者您可以查看缓存该临时表。要么将其缓存在某个中间层内存中,要么将该临时表设为永久表并仅在其他表中的行发生更改时更新它?
    • 如果我无法在使用索引方面获得显着改进,我将使用前一条评论中描述的连接的动态过滤 - 我想避免使用持久性缓存表!跨度>
    • 我尝试了动态过滤,我正在使用完全填充的 db 9k 而不是 70k - 好一点,但没有我预期的那么多。我正在重新考虑整个事情 - 也许我可以将表达式 (e) 表与 e.articleId = a.articleId 上的属性 (e) 表连接起来,然后加入 a.attributeName 上的名称-值对表 (t) = t.name 和 a.attributeValue = t.value 以更少的计算实现相同的过滤!
    • 我开始另一个问题 --> *.com/questions/923136/…
    【解决方案5】:

    另一件事是添加一些索引,如下所示:

    attributes.{attributeId, attributeName, attributeValue}
    expressions.{attributeId, expressionID}
    

    这太骇人听闻了!但如果是万不得已的话,这很有用。

    这样做是创建一个可以由索引“完全回答”的查询计划。通常,索引实际上会在上述查询中导致双 I/O:一个命中索引(即探测表),另一个获取索引引用的实际行(提取属性名称等)。

    如果“属性”或“表达式”是一个宽表,这将特别有用。也就是说,从表中获取行的成本很高。

    最后,加快查询速度的最佳方法是添加 WHERE 子句!

    【讨论】:

    • 这些索引会在插入时杀死我吗?关于 WHERE - 我正在使用这个连接来填充一个临时表,我将使用它来查找给定名称-值对(属性)集的表达式 ID(如果有)。所以我想我可以在这个查询中使用 OR disjuncts attributeNames+AttributeValues 来加快它的速度
    • 我必须动态附加 OR 分离,因为我需要像 WHERE (attributeName = 'X' AND attributeValue = 'Y') OR (attributeName = 'Z' AND attributeValue = ' W') ...等等!所以我可能会浪费时间循环遍历具有名称值对的表并构建这些子句
    • 插入时总是需要权衡索引。同样(不幸的是),没有万能的答案。如果您只有一个或两个索引,并且鉴于此索引不是集群的,那么它可能不会杀死您。也就是说,这是一个非常适合特定查询的索引,因此请自行决定使用。
    • 至于 WHERE 子句——如果您有(相对)唯一的名称和值,那么在名称和值上放置索引并多次发出查询可能会更快,每次“或”,并将结果合并到您的(C#/ASP/其他)代码中。尤其是对于真正唯一的值,每个索引只有几个 I/O 才能到达您的行(假设您的表只有 ~1k 行)
    • table 有大约 10k 行,并且存在大量重复的名称-值。无论如何,我可能会针对该特定问题提出另一个问题 - 我的意思是这个问题只是作为简单连接的性能建议
    【解决方案6】:

    如果我正确理解了您的架构,那么您是在说您的表看起来像这样:

    Expressions: PK - ExpressionID, AttributeID
    Attributes:  PK - AttributeID
    

    假设每个 PK 都是一个聚集索引,这仍然意味着需要对 Expressions 表进行索引扫描。您可能需要考虑在 Expressions 表上创建一个索引,例如:AttributeID、ExpressionID。这将有助于停止当前发生的索引扫描。

    【讨论】:

    • 你的理解是正确的。您的意思是在 (ExpressionId, AttributeId) 的表达式上添加一个非聚集索引,而不是已经存在的聚集索引?
    【解决方案7】:

    提示,

    如果您想使用连接加快查询速度:

    对于“内连接/连接”,
    不要使用 where 条件,而是在“ON”条件下使用它。
    例如:

             select id,name from table1 a  
           join table2 b on a.name=b.name
           where id='123'
    
         Try,
    
            select id,name from table1 a  
           join table2 b on a.name=b.name and a.id='123'
    

    对于“左/右连接”,
    不要在“ON”条件下使用,因为如果使用左/右连接,它将获取任何一张表的所有行。所以,在“ON”条件下不要使用它。所以,尝试使用“Where”条件

    【讨论】: