SQL Server中的INNER JOIN与LEFT JOIN性能

时间:2010-04-28 03:36:04

标签: 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

9 个答案:

答案 0 :(得分:374)

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

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

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


修改

进一步反思这一点,我可以想到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选择更差执行计划的唯一情况。

答案 1 :(得分:109)

有一个重要的场景可能会导致外部联接比尚未讨论的内部联接更快。

当使用外部联接时,如果连接列是外部表的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,具体取决于存在的约束。

如果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&gt,你似乎应该可以安全地用OUTER JOIN替换INNER JOIN。 ;对于where子句,它不是NULL。

答案 2 :(得分:21)

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

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

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

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

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

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

答案 3 :(得分:16)

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

如果INNER JOIN开始执行速度与LEFT JOIN一样快,那是因为:

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

不知道这是否能解答您的问题,但我曾经参与过一个项目,该项目包含高度复杂的查询计算,这完全搞砸了优化器。我们遇到FORCE ORDER会将查询的执行时间从5分钟减少到10秒的情况。

答案 4 :(得分:7)

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

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

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

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

答案 5 :(得分:5)

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

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

答案 6 :(得分:4)

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

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

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

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

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

答案 7 :(得分:1)

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

如果您不包含左连接表的项目,则在select语句中,左连接将比具有内连接的相同查询更快。

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

答案 8 :(得分:0)

从我的比较中,我发现它们具有完全相同的执行计划。共有三种情况:

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

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

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

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