SQL Server中INNER JOIN与LEFT JOIN的性能


259

我创建了在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

1
您从投射任何属性coUOM吗?如果没有,您也许可以使用半联接。如果是,则可以UNION用作替代方法。仅张贴您的FROM条款是这里的信息不足。
2011年

1
我经常想知道这个(因为我一直都在看)。
Paul Draper

1
您是否错过了简短模式中的订购依据?我最近遇到了一个问题,其中将INNER JOIN更改为LEFT OUTER JOIN可使查询从3分钟加速到10秒。如果您的查询中确实有订购者,我将作进一步解释。似乎所有答案并没有真正解释我所遇到的情况。
Phuah Yee Keat 2015年

Answers:


403

A LEFT JOIN绝对不会比A 快INNER JOIN。实际上,它比较慢。根据定义,外部联接(LEFT JOINRIGHT JOIN)必须完成所有工作,INNER JOIN再加上对结果进行null扩展的额外工作。仅仅由于结果集的大小,也期望返回更多的行,从而进一步增加了总的执行时间。

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

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


编辑:

进一步思考一下,我想到一种情况,在这种情况下,a LEFT JOIN可能比快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

如果运行此命令并查看执行计划,则会发现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 JOIN不是选择更糟糕的执行计划LEFT JOIN


4
还有另一种情况可能导致外部联接比内部联接的性能更好。请参阅下面的答案。
dbenham 2011年

12
我想指出的是,基本上没有数据库文档来支持内部连接和外部连接性能不同的观点。由于数据量和结果集的大小,外部联接比内部联接要贵一些。但是,两种连接类型的基础算法(msdn.microsoft.com/zh-cn/library/ms191426(v=sql.105).aspx)是相同的。当它们返回相似数量的数据时,性能应该相似。
Gordon Linoff 2012年

3
@Aaronaught。。。该评论在一条评论中被引用,该评论表示“外部联接的性能明显比内部联接差”。我评论只是为了确保这种误解不会扩散。
Gordon Linoff 2012年

16
我认为这个答案在一个重要方面具有误导性:因为它指出“左联接绝对不比内联接快”。这行是不正确的。从理论上讲,它不比INNER JOIN快。它不是 “绝对不会更快”。该问题专门是性能问题。在实践中,我现在已经看到一些系统(非常大的公司!),与OUTER JOIN相比,INNER JOIN的运行速度慢得可笑。理论和实践是完全不同的东西。
David Frenkel

5
@DavidFrenkel:那是极不可能的。如果您认为这样的差异是可能的,我希望查看A / B与执行计划的比较。可能与缓存的查询/执行计划或错误的统计信息有关。
亚罗诺(Aaronaught)2013年

127

有一种重要的情况可能导致外部联接比尚未讨论的内部联接快。

使用外部联接时,如果联接列是外部表的PK,并且外部表本身都没有引用任何外部表列,则优化程序始终可以从执行计划中删除外部联接表。例如SELECT A.* FROM A LEFT OUTER JOIN B ON A.KEY=B.KEY,B.KEY是B的PK。Oracle(我相信我使用的是版本10)和Sql Server(我使用的是2008 R2)都修剪执行计划中的表B。

对于内部联接,情况不一定如此:SELECT A.* FROM A INNER JOIN B ON A.KEY=B.KEY根据存在的约束,执行计划中可能需要B,也可能不需要B。

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

如果A.KEY是引用B.KEY的强制性外键,那么优化器可以自由地将B从计划中删除,因为约束条件保证了该行的存在。但是,仅仅因为优化器可以从计划中删除表,并不意味着会。SQL Server 2008 R2不会从计划中删除B。Oracle 10确实从计划中删除了B。在这种情况下,很容易看出外部联接在SQL Server上的表现如何优于内部联接。

这是一个简单的示例,对于独立查询不实用。如果不需要,为什么要加入表格?

但这在设计视图时可能是非常重要的设计考虑因素。通常,会构建一个“做一切”视图,该视图将用户可能需要的与中央表有关的所有内容结合在一起。(特别是如果有一些天真的用户在执行不了解关系模型的临时查询),该视图可能包括许多表中的所有相关列。但是最终用户可能只能访问视图中表的子集的列。如果表是通过外部联接进行连接的,那么优化器可以(并且确实)从计划中删除不需要的表。

确保使用外部联接的视图给出正确的结果至关重要。正如Aaronaught所说-您不能盲目地用OUTER JOIN代替INNER JOIN并期望得到相同的结果。但是有时候在使用视图时出于性能原因它可能很有用。

最后一点-鉴于上述情况,我尚未测试对性能的影响,但是从理论上讲,如果您还添加条件<FOREIGN_KEY> IS NOT NULL,则应该可以用OUTER JOIN安全地替换INNER JOIN到where子句。


5
在构建极其动态的查询时,我实际上遇到了这个问题。我离开了一个正在使用的内部联接,而不是从中提取数据,当我将其切换到左侧联接(出于好奇心)时,查询实际上运行得更快。
Erik Philips

1
编辑-阐明了优化器从执行计划中删除外部联接表所必须存在的条件。
dbenham

2
对您的答案的一点点澄清:当外键列不可为空时,INNER JOIN和LEFT JOIN在语义上是等效的(即,您建议的WHERE子句是多余的);唯一的区别是执行计划。
道格拉斯

2
尽管这确实显示了一个看似微不足道的例子,但这是一个非常有见地的答案!
pbalaga 2015年

6
+1:我似乎在一些查询中遇到了这个问题,在这些查询中我使用了一些非常大的表的内部联接。内部联接导致查询计划中的tempdb溢出(出于上述原因,我认为这是我的服务器,而我的服务器缺少RAM来将所有内容保存在内存中)。切换到左联接消除了向tempdb的溢出,结果是我现在20-30秒的查询中的一些现在只用了不到一秒的时间即可运行。这是一个非常重要的陷阱,因为大多数人似乎完全假设内部联接更快。
phosplait 16/09/13

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 JOIN s,联接顺序无关紧要。这使查询优化器可以自由地按自己认为合适的顺序对联接进行排序,因此问题可能取决于优化器。
  • 使用LEFT JOIN,不是这种情况,因为更改联接顺序将更改查询的结果。这意味着引擎必须遵循您在查询中提供的联接顺序,这可能比优化后的顺序更好。

不知道这是否能回答您的问题,但我曾经在一个项目中进行过非常复杂的查询计算,这完全弄乱了优化程序。在某些情况下,a FORCE ORDER会将查询的执行时间从5分钟减少到10秒。


9

在左外部连接和内部连接之间进行了许多比较,但未能找到一致的区别。有很多变量。我正在处理具有数千个表的报表数据库,其中许多表具有大量字段,并且随着时间的推移发生了许多更改(供应商版本和本地工作流)。无法创建覆盖索引的所有组合来满足如此广泛的查询和处理历史数据的需求。看到内部查询会破坏服务器性能,因为内部连接了两个大表(几百万到几千万行),这两个表都拉动了大量字段并且不存在覆盖索引。

不过,最大的问题似乎不在上面的讨论中。也许您的数据库经过精心设计,包含触发器和经过精心设计的事务处理,以确保获得良好的数据。我的经常有NULL值,这是不希望的。是的,表定义可以强制为空,但是在我的环境中这不是一个选择。

因此,问题是...您是否仅出于速度而设计查询,对于每分钟运行数千次相同代码的事务处理而言,优先级更高。还是您追求左外部联接将提供的准确性。请记住,内部联接必须在两端都找到匹配项,因此,意外的NULL不仅会从两个表中删除数据,而且可能会删除整个信息行。它发生得很好,没有错误消息。

您可以很快获得90%的所需数据,而不会发现内部联接已静默删除了信息。有时内部联接可能会更快,但是我相信除非做出了执行计划的审查,否则任何人都不会做出这种假设。速度很重要,但准确性更重要。


8

您的性能问题很可能是由于您正在执行的连接数以及要连接的列是否具有索引所致。

最坏的情况是,您很容易为每个联接进行9次全表扫描。


7

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

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

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

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

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


1
您有关“外部连接”的陈述具有误导性,并且可能不正确。外层表示另一侧的数据不必存在-如果不替代NULL。在特定情况下,RDBMS可能会“跳过”它们(请参见dbenham的上述答复)。但是,外部与内部会导致您的查询返回截然不同的结果。INNER表示-给出项目在A和B中的结果。LEFT OUTER表示所有A,如果存在则表示B。第一种情况-您得到一些行,第二个情况中得到所有行。
ripvlan

1
@ripvlan当然,外部联接和内部联接并不总是可以互换的。最初的问题是关于性能的,这意味着我们正在谈论的情况是,任何一个联接都将返回相同的结果集。
MarredCheese

1
是的,并且-OUTER可能会导致性能问题,因为它将导致返回所有行(更多数据)。您假设查询会产生相同的输出是一个公平的假设-但是,在一般情况下并不适用于每个数据库设计,这是不正确的。对于那些不是100%熟悉关系代数的人,可能会引起悲伤。我的观点仅仅是为阅读此建议的人们提供更多见识,并且“左/右”不会神奇地解决问题,而可能会引起更多的问题。这是300级的力量:-)
ripvlan

2

当检查内部联接是否比左联接快时,我在SQL Server中发现了一些有趣的东西。

如果不包括左联接表的项目,则在select语句中,左联接将比具有内部联接的查询更快。

如果确实在select语句中包括左联接表,则具有相同查询的内部联接将等于或快于左联接。


0

通过比较,我发现它们具有完全相同的执行计划。有以下三种情况:

  1. 如果它们返回相同的结果,则它们具有相同的速度。但是,我们必须记住,它们不是相同的查询,并且LEFT JOIN可能会返回更多结果(当不满足某些ON条件时)---这就是它通常较慢的原因。

  2. 当主表(执行计划中的第一个非常量一个表)具有限制性条件(WHERE id =?)且对应的ON条件处于NULL值时,“右”表未连接---这是当LEFT JOIN更快。

  3. 如第1点所述,通常INNER JOIN的限制更严格,返回的结果更少,因此速度更快。

两者都使用(相同)索引。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.