SQL Server中的INNER JOIN与LEFT JOIN性能

Ano*_*ous 247 sql sql-server performance

我已经为9个表创建了使用INNER JOIN的SQL命令,无论如何这个命令需要很长时间(超过五分钟).因此,我的民众建议我将INNER JOIN更改为LEFT JOIN因为LEFT JOIN的表现更好,尽管我所知道的第一次.我改变后,查询的速度显着提高.

我想知道为什么LEFT JOIN比INNER JOIN快?

我的SQL命令如下所示: SELECT * FROM A INNER JOIN B ON ... INNER JOIN C ON ... INNER JOIN D依此类推

更新: 这是我的架构的简要说明.

FROM sidisaleshdrmly a -- NOT HAVE PK AND FK
    INNER JOIN sidisalesdetmly b -- THIS TABLE ALSO HAVE NO PK AND FK
        ON a.CompanyCd = b.CompanyCd 
           AND a.SPRNo = b.SPRNo 
           AND a.SuffixNo = b.SuffixNo 
           AND a.dnno = b.dnno
    INNER JOIN exFSlipDet h -- PK = CompanyCd, FSlipNo, FSlipSuffix, FSlipLine
        ON a.CompanyCd = h.CompanyCd
           AND a.sprno = h.AcctSPRNo
    INNER JOIN exFSlipHdr c -- PK = CompanyCd, FSlipNo, FSlipSuffix
        ON c.CompanyCd = h.CompanyCd
           AND c.FSlipNo = h.FSlipNo 
           AND c.FSlipSuffix = h.FSlipSuffix 
    INNER JOIN coMappingExpParty d -- NO PK AND FK
        ON c.CompanyCd = d.CompanyCd
           AND c.CountryCd = d.CountryCd 
    INNER JOIN coProduct e -- PK = CompanyCd, ProductSalesCd
        ON b.CompanyCd = e.CompanyCd
           AND b.ProductSalesCd = e.ProductSalesCd 
    LEFT JOIN coUOM i -- PK = UOMId
        ON h.UOMId = i.UOMId 
    INNER JOIN coProductOldInformation j -- PK = CompanyCd, BFStatus, SpecCd
        ON a.CompanyCd = j.CompanyCd
            AND b.BFStatus = j.BFStatus
            AND b.ProductSalesCd = j.ProductSalesCd
    INNER JOIN coProductGroup1 g1 -- PK = CompanyCd, ProductCategoryCd, UsedDepartment, ProductGroup1Cd
        ON e.ProductGroup1Cd  = g1.ProductGroup1Cd
    INNER JOIN coProductGroup2 g2 -- PK = CompanyCd, ProductCategoryCd, UsedDepartment, ProductGroup2Cd
        ON e.ProductGroup1Cd  = g2.ProductGroup1Cd
Run Code Online (Sandbox Code Playgroud)

Aar*_*ght 394

A LEFT JOIN绝对不比a快INNER JOIN.事实上,它更慢; 根据定义,外连接(LEFT JOINRIGHT JOIN)必须完成所有的工作,INNER JOIN以及对结果进行空值扩展的额外工作.还期望返回更多行,仅仅由于结果集的较大尺寸而进一步增加总执行时间.

(而且即使LEFT JOIN 在更快的特定情况下,由于一些因素难以想象的融合,它不是功能上等同于INNER JOIN,所以你不能简单地去更换一个与其他的所有实例!)

很可能您的性能问题存在于其他地方,例如没有正确的候选键或外键索引.9个表是非常多的加入,因此减速几乎可以在任何地方.如果您发布架构,我们可能会提供更多详细信息.


编辑:

为了进一步反思这一点,我可以想到一个LEFT JOIN可能比a 更快的情况INNER JOIN,那就是:

  • 有些表非常小(比如10行以下);
  • 表没有足够的索引来覆盖查询.

考虑这个例子:

CREATE TABLE #Test1
(
    ID int NOT NULL PRIMARY KEY,
    Name varchar(50) NOT NULL
)
INSERT #Test1 (ID, Name) VALUES (1, 'One')
INSERT #Test1 (ID, Name) VALUES (2, 'Two')
INSERT #Test1 (ID, Name) VALUES (3, 'Three')
INSERT #Test1 (ID, Name) VALUES (4, 'Four')
INSERT #Test1 (ID, Name) VALUES (5, 'Five')

CREATE TABLE #Test2
(
    ID int NOT NULL PRIMARY KEY,
    Name varchar(50) NOT NULL
)
INSERT #Test2 (ID, Name) VALUES (1, 'One')
INSERT #Test2 (ID, Name) VALUES (2, 'Two')
INSERT #Test2 (ID, Name) VALUES (3, 'Three')
INSERT #Test2 (ID, Name) VALUES (4, 'Four')
INSERT #Test2 (ID, Name) VALUES (5, 'Five')

SELECT *
FROM #Test1 t1
INNER JOIN #Test2 t2
ON t2.Name = t1.Name

SELECT *
FROM #Test1 t1
LEFT JOIN #Test2 t2
ON t2.Name = t1.Name

DROP TABLE #Test1
DROP TABLE #Test2
Run Code Online (Sandbox Code Playgroud)

如果你运行它并查看执行计划,你会发现INNER JOIN查询的成本确实高于LEFT JOIN,因为它满足上面的两个标准.这是因为SQL Server想要为它做一个哈希匹配INNER JOIN,但是对于LEFT JOIN; 前者通常要快得多,但由于行数太小而且没有索引可供使用,因此散列操作最终成为查询中最昂贵的部分.

您可以通过使用您喜欢的编程语言编写程序来在具有5个元素的列表上执行大量查找,而不是具有5个元素的哈希表,从而看到相同的效果.由于大小,哈希表版本实际上较慢.但是将它增加到50个元素或5000个元素,并且列表版本减慢到爬行速度,因为它是散列表的O(N)与O(1).

但是将此查询更改为ID列而不是,Name您将看到一个非常不同的故事.在这种情况下,它确实嵌套两个查询循环,但INNER JOIN版本能够取代聚簇索引扫描的一个与寻求-这意味着这简直就一个数量级有大量行的速度更快.

所以结论或多或少是我上面提到的几段; 这几乎肯定是索引或索引覆盖问题,可能与一个或多个非常小的表结合.只有这些情况下使用的SQL Server 可能有时会选择一个更糟糕的执行计划INNER JOINLEFT JOIN.

  • 我认为这个答案在一个重要方面具有误导性:因为它表明"左边连接绝对不比内部连接快".这条线不正确.它理论上*不比INNER JOIN快.它不是*"绝对不会更快".问题特别是性能问题.在实践中,我现在已经看到了一些系统(非常大的公司!),与OUTER JOIN相比,INNER JOIN的速度非常慢.理论与实践是截然不同的事情. (15认同)
  • 我想指出,基本上没有数据库文档可以支持内部联接和外部联接性能不同的想法.由于数据量和结果集的大小,外连接比内连接稍贵.但是,两种类型的连接的基础算法(http://msdn.microsoft.com/en-us/library/ms191426(v=sql.105).aspx)是相同的.返回相似数量的数据时,性能应该相似. (12认同)
  • 还有另一种情况可能导致OUTER JOIN比INNER JOIN表现更好.请参阅下面的答案. (4认同)
  • @DavidFrenkel:这是不太可能的.如果您认为可能出现这种差异,我会要求查看执行计划的A/B比较.可能它与缓存的查询/执行计划或错误的统计信息有关. (4认同)
  • @Aaronaught...这个答案在评论中被引用,该评论说"外连接比内连接表现更差".我评论只是为了确保这种误解不会扩散. (3认同)
  • @GordonLinoff:这就是答案在第一段中已经说过了。 (2认同)
  • @GordonLinoff-算法并不完全相同。嵌套循环不支持正确的外部联接,外部联接的交换方式与内部联接的交换方式不同。[此处的示例](http://stackoverflow.com/a/15050606/73226) (2认同)
  • @MartinSmith。。。谢谢你的支持。我以为优化器会在内部将右侧的外部联接转换为左侧的外部联接并继续进行优化。我发现我的评论在理论上是错误的,对此我感到很不幸。这意味着根据查询的编写方式,存在一些未考虑的优化路径。 (2认同)

dbe*_*ham 116

有一个重要的场景可能导致外连接比尚未讨论的内连接更快.

使用外部联接时,如果连接列是外部表的PK,则优化程序始终可以从执行计划中删除外部联接表,并且不从外部表中选择任何列.例如SELECT A.* FROM A LEFT OUTER JOIN B ON A.KEY=B.KEY,B.KEY是B的PK.两个Oracle(我相信我使用的是版本10)和Sql Server(我使用的是2008 R2)来自执行计划的prune表B.

内部联接也不一定如此:SELECT A.* FROM A INNER JOIN B ON A.KEY=B.KEY执行计划中可能需要或不需要B,具体取决于存在的约束.

如果A.KEY是引用B.KEY的可空外键,则优化器不能从计划中删除B,因为它必须确认每个A行都存在B行.

如果A.KEY是引用B.KEY的强制外键,则优化器可以自由地从计划中删除B,因为约束保证了行的存在.但仅仅因为优化器可以从计划中删除表,并不意味着它会.SQL Server 2008 R2不会从计划中删除B. Oracle 10将B从计划中删除.在这种情况下,很容易看到外连接如何在SQL Server上执行内部连接.

这是一个简单的示例,对于独立查询不实用.如果你不需要,为什么要加入桌子呢?

但在设计视图时,这可能是一个非常重要的设计考虑因素.通常会构建一个"do-everything"视图,它将用户可能需要的所有内容与中央表相关联.(特别是如果有天真的用户正在进行不了解关系模型的即席查询)视图可能包含许多表中的所有相关列.但最终用户可能只访问视图中表的子集中的列.如果表与外连接连接,那么优化器可以(并且确实)从计划中删除不需要的表.

确保使用外部联接的视图提供正确的结果至关重要.正如Aaronaught所说 - 你不能盲目地将OUTER JOIN替换为INNER JOIN并期望得到相同的结果.但有时候,在使用视图时,由于性能原因,它可能很有用.

最后一点 - 我没有根据上述情况测试对性能的影响,但理论上,如果你还添加条件<FOREIGN_KEY> IS NOT NULL,你似乎应该可以安全地用OUTER JOIN替换INNER JOIN.到where子句.

  • +1:我似乎在几个查询中遇到了这个问题,我在内部连接中使用了一些非常大的表.内部联接导致查询计划中的tempdb溢出(我假设上面提到的原因 - 我的服务器缺少RAM来保存内存中的所有内容).切换到左连接消除了溢出到tempdb,结果是我的一些20-30秒查询现在在几分之一秒内运行.这是一个非常重要的问题,因为大多数人似乎做出了内部联接更快的一揽子假设. (6认同)
  • 在构建极其动态的查询时,我实际上遇到了这个问题.我离开了我正在使用的INNER JOIN而不是从中提取数据,当我将其切换到LEFT JOIN(出于剪切好奇心)时,查询实际上运行得更快. (4认同)
  • 对您的答案的一点点澄清:当外键列不可为空时,INNER JOIN和LEFT JOIN在语义上是等效的(即,您建议的WHERE子句是多余的);唯一的区别是执行计划。 (2认同)
  • 尽管这确实显示了一个看似微不足道的例子,但这是一个非常有见地的答案! (2认同)

Kva*_*asi 23

如果一切正常,它应该不应该,但是我们都知道一切都不会以它应该的方式工作,尤其是在查询优化器,查询计划缓存和统计时.

首先,我建议重建索引和统计信息,然后清除查询计划缓存,以确保不会搞砸.然而,即使这样做,我也遇到了问题.

我遇到过一些左连接比内连接快的情况.

根本原因是:如果你有两个表并且你加入一个带索引的列(在两个表上).如果你循环遍历表1中的索引中的条目并与表2上的索引匹配,内部联接将产生相同的结果,就好像你会反过来一样:循环表2上的索引中的条目并与索引匹配在表一.问题是当您有误导性统计信息时,查询优化器将使用索引的统计信息来查找具有最少匹配条目的表(基于您的其他条件).如果你有两个表,每个表有100万个,在表1中你有10行匹配,在表2中你有100000行匹配.最好的方法是在表1上进行索引扫描,在表2中进行10次匹配.反过来的是一个索引扫描,它循环超过100000行,并尝试匹配100000次,只有10次成功.因此,如果统计信息不正确,优化器可能会选择错误的表和索引进行循环.

如果优化器选择按照编写顺序优化左连接,则它将比内连接执行得更好.

但是,优化器还可以优化地将左连接优化为左半连接.要使它选择你想要的那个,你可以使用强制命令提示.


小智 18

OPTION (FORCE ORDER)在末尾尝试两个查询(内部和左侧连接的查询)并发布结果.OPTION (FORCE ORDER)是一个查询提示,强制优化器使用您在查询中提供的连接顺序构建执行计划.

如果INNER JOIN开始表现得那么快LEFT JOIN,那是因为:

  • 在完全由INNER JOINs 组成的查询中,连接顺序无关紧要.这使查询优化器可以自由地按照它认为合适的顺序对连接进行排序,因此问题可能依赖于优化器.
  • 随着LEFT JOIN,事实并非如此,因为改变连接顺序将改变查询的结果.这意味着引擎必须遵循您在查询中提供的连接顺序,这可能优于优化的连接顺序.

不知道这是否能回答你的问题,但我曾经在一个项目中进行了高度复杂的查询计算,这完全搞砸了优化器.我们遇到的情况是FORCE ORDER将查询的执行时间从5分钟减少到10秒.


小智 9

在左外连接和内连接之间进行了多次比较,但未能找到一致的差异.有很多变数.我正在处理一个报告数据库,其中有数千个表,其中包含大量字段,随时间变化很多(供应商版本和本地工作流程).无法创建覆盖索引的所有组合以满足各种查询和处理历史数据的需要.已经看到内部查询会破坏服务器性能,因为两个大的(数百万到数千万行)表是内部连接,同时拉动大量字段并且不存在覆盖索引.

但是,最大的问题似乎并没有在上面的讨论中表现出来.也许您的数据库设计良好,具有触发器和精心设计的事务处理,以确保良好的数据 我经常在没有预料到的情况下使用NULL值.是的,表定义可以强制执行no-Null,但这不是我环境中的选项.

所以问题是......你是否只为速度设计查询,对于每分钟运行相同代码数千次的事务处理来说,优先级更高.或者你是否考虑左外连接将提供的准确性.请记住,内部联接必须在两侧找到匹配项,因此意外的NULL不仅会从两个表中删除数据,而且可能会删除整行信息.它发生得非常好,没有错误消息.

您可以非常快速地获取90%的所需数据,而不是发现内部联接已经默默地删除了信息.有时内连接可以更快,但我不相信任何人做出这种假设,除非他们已经审查了执行计划.速度很重要,但准确性更重要.


edd*_*ves 8

您的性能问题更可能是因为您正在进行的连接数以及您加入的列是否具有索引.

最糟糕的情况是,您可以轻松地为每个连接执行9次全表扫描.


Mar*_*ese 7

在视图中使用外部联接时,可以提供出色的性能。

假设您有一个包含视图的查询,并且该视图由10个连接在一起的表组成。假设您的查询仅碰巧使用了这10个表中的3个列。

如果这10个表已经内部连接在一起,那么即使查询本身不需要10个表中的7个,查询优化器也必须将它们全部连接在一起。这是因为内部联接本身可能会过滤掉数据,从而使它们对于计算至关重要。

如果将这10个表进行了外部联接,则查询优化器实际上只会联接必要的表:在这种情况下,其中10个表中有3个。这是因为联接本身不再过滤数据,因此可以跳过未使用的联接。

来源:http//www.sqlservercentral.com/blogs/sql_coach/2010/07/29/poor-little-misunderstood-views/

  • @ripvlan 当然,外连接和内连接并不总是可以互换的。最初的问题是关于性能的,这意味着我们正在讨论任一连接将返回相同结果集的情况。 (2认同)