SQL Server中的INNER JOIN与LEFT JOIN性能

我创build了SQL命令使用INNER JOIN 9个表,无论如何这个命令需要很长时间(超过五分钟)。 所以我的民众build议我把INNER JOIN改为LEFT JOIN,因为LEFT JOIN的performance更好,尽pipe我知道它是第一次。 改变之后,查询速度明显提高。

我想知道为什么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 

LEFT JOIN绝对不会比INNER JOIN快。 事实上,速度较慢, 根据定义,外部LEFT JOINLEFT JOINRIGHT JOIN )必须完成INNER JOIN所有工作,再加上扩展结果的额外工作。 由于结果集的大小更大,因此也会返回更多行,进一步增加总执行时间。

(即使由于某些难以想象的因素汇合,在特定情况下LEFT JOIN速度更快,但它在function上不等同于INNER JOIN ,因此不能简单地将其replace为一个实例)。

您的性能问题很可能存在于别处,比如没有正确的索引候选键或外键。 9张表是相当多的join,所以放缓几乎可以在任何地方。 如果您发布您的架构,我们可能会提供更多的细节。


编辑:

进一步反思,我可以想到一个情况下,一个LEFT 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版本能够用searchreplace聚集索引扫描中的一个 – 这意味着对于大量的行来说,这会更快一个数量级。

所以这个结论或多或less是我上面几段提到的; 这几乎肯定是索引或索引覆盖问题,可能与一个或多个非常小的表相结合。 这些是SQL Server有时可能selectINNER JOIN执行计划比LEFT JOIN更差的唯一情况。

有一个重要的情况可能会导致一个外部连接比内部连接还没有被讨论。

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

对于一个内部SELECT A.* FROM A INNER JOIN B ON A.KEY=B.KEY来说,这也不一定是正确的: SELECT A.* FROM A INNER JOIN B ON A.KEY=B.KEY一个内部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 DOES从计划中删除B. 在这种情况下,很容易看到外连接如何在SQL Server上执行内连接。

这是一个简单的例子,对于一个独立的查询来说并不实用。 为什么join到一张桌子,如果你不需要?

但是在devise视图时,这可能是非常重要的devise考虑因素。 通常会build立一个“万能”视图,将用户可能需要的所有内容与中心表相关联。 (特别是如果有天真的用户做不了解关系模型的即席查询)视图可能包括来自许多表的所有相关列。 但是,最终用户只能访问视图中表格子集的列。 如果这些表与外连接相连接,那么优化器可以(并且)从计划中删除不需要的表。

确保使用外部连接的视图提供正确的结果是至关重要的。 正如Aaronaught所说的 – 你不能盲目地用OUTER JOINreplaceINNER JOIN,并期待相同的结果。 但是在使用视图的时候,有些时候它可以用于性能方面。

最后一个注意事项 – 根据上述情况,我没有testing对性能的影响,但从理论上讲,如果还添加了条件<FOREIGN_KEY> IS NOT NULL,您应该能够安全地将INNER JOINreplace为OUTER JOIN到where子句。

如果一切正常,不应该这样做,但是我们都知道一切都不应该如此,特别是当涉及到查询优化器,查询计划caching和统计信息时。

首先,我会build议重build索引和统计信息,然后清除查询计划caching,以确保不会搞砸。 但是,即使这样做,我也遇到了问题。

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

其根本原因是这样的:如果你有两个表,你join一个索引列(在两个表上)。 无论循环表1中的索引中的条目并与表2中的索引相匹配,内部连接都会产生相同的结果,就好像您会做相反的事情:在表2上的索引中循环条目并与索引匹配在表一中。 问题是当你误导统计信息时,查询优化器将使用索引的统计信息来查找包含最less匹配条目的表(基于其他条件)。 如果你有两个表,每个表中有一百万,表1中有10行匹配,表二中有100000行匹配。 最好的办法是对表1进行索引扫描,并在表2中匹配10次。 反过来,这将是一个索引扫描,循环超过100000行,并尝试匹配100000次,只有10个成功。 所以如果统计不正确,优化器可能会select错误的表和索引来循环。

如果优化器select优化左连接的顺序写入,它将比内连接执行更好。

但是,优化器也可以将左连接优化为左半连接。 要使其select你想要的,你可以使用强制命令提示。

在最后使用OPTION (FORCE ORDER)尝试两个查询(带有内连接和左连接的查询OPTION (FORCE ORDER) ,并发布结果。 OPTION (FORCE ORDER)是一个查询提示,它强制优化器用您在查询中提供的连接顺序构build执行计划。

如果INNER JOIN启动速度与LEFT JOIN一样快,那是因为:

  • 在完全由INNER JOIN组成的查询中,连接顺序无关紧要。 这为查询优化器提供了自由度,以便按照它认为合适的顺序sorting连接,所以问题可能依赖于优化器。
  • 使用LEFT JOIN ,情况并非如此,因为更改连接顺序将会改变查询的结果。 这意味着引擎必须遵循您在查询中提供的连接顺序,这可能比优化后的连接顺序更好。

不知道这是否回答你的问题,但我曾经在一个项目中,高度复杂的查询进行计算,这完全搞砸了优化。 我们曾经有过一个FORCE ORDER会把查询的执行时间从5分钟缩短到10秒的情况。

在左外连接和内连接之间做了一些比较,并没有find一个consisten差异。 有很多变数。 我正在使用一个包含数千个表的许多报表数据库,其中包含大量字段,随着时间的推移(供应商版本和本地工作stream程)发生许多变化。 不可能创build覆盖索引的所有组合来满足这种各种各样的查询和处理历史数据的需要。 已经看到内部查询终止服务器的性能,因为两个大的(数百万到数千万行)表都是内部连接的,既拉大量的字段又不存在覆盖索引。

但是,最大的问题似乎并没有出现在上面的讨论中。 也许你的数据库devise良好,触发器和devise良好的事务处理,以确保良好的数据。 我经常有空值,他们不是预期的。 是的,表定义可以强制执行非空值,但在我的环境中这不是一个选项。

所以问题是…你是否仅仅为了速度而devise你的查询,这个事务处理的优先级是每分钟运行相同的代码几千次。 或者你去为左外连接将提供的准确性。 请记住,内部联接必须在两边都find匹配,所以意外的NULL不仅会从两个表中删除数据,而且可能会删除整行信息。 它发生得很好,没有错误信息。

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

您的性能问题更可能是由于您正在进行的连接数量以及您要连接的列是否具有索引。

最坏的情况下,你可以轻松地为每个连接做9个整个表扫描。

在视图中使用外连接可以提供卓越的性能。

假设你有一个涉及视图的查询,并且该视图由10个连接在一起的表组成。 假设你的查询只使用这10个表中的3个。

如果这10个表已经内部连接在一起,那么查询优化器将不得不将它们全部连接起来,即使您的查询本身并不需要十个表中的七个。 这是因为内部连接本身可能会过滤掉数据,这使得它们成为计算的基础。

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

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