SQL连接和SQL子查询(性能)?

150

我想知道如果我有一个类似于这样的连接查询:

Select E.Id,E.Name from Employee E join Dept D on E.DeptId=D.Id

并且需要一个子查询,类似这样的语法 -

Select E.Id,E.Name from Employee Where DeptId in (Select Id from Dept)

考虑到性能,两个查询中哪一个更快?为什么

还有没有时间我应该更喜欢一个而不是另一个?

如果这太琐碎并且已经问过了,请原谅,但我对此感到困惑。此外,如果您可以建议我应该使用的工具来衡量两个查询的性能,那将是很棒的。非常感谢!


6
@Lucero,这个问题的标签是sql-server-2008,而你提到的帖子标签是MySql。你可以推断出两个关系型数据库管理系统的性能优化方法不同,所以答案可能会有所不同。请注意区分。 - Francois Botha
8个回答

59

嗯,我认为这是一个“千古难题”。答案是:“这取决于情况!”。 性能是一个非常微妙的问题,仅仅说“永远不要使用子查询,总是连接”太过幼稚。 在以下链接中,您会找到一些基本最佳实践,我发现它们非常有帮助:

我有一个包含50000个元素的表,我要寻找的结果是739个元素。

我最初的查询是:

SELECT  p.id,
    p.fixedId,
    p.azienda_id,
    p.categoria_id,
    p.linea,
    p.tipo,
    p.nome
FROM prodotto p
WHERE p.azienda_id = 2699 AND p.anno = (
    SELECT MAX(p2.anno) 
    FROM prodotto p2 
    WHERE p2.fixedId = p.fixedId 
)

执行该操作共花费7.9秒。

最终我的问题是:

SELECT  p.id,
    p.fixedId,
    p.azienda_id,
    p.categoria_id,
    p.linea,
    p.tipo,
    p.nome
FROM prodotto p
WHERE p.azienda_id = 2699 AND (p.fixedId, p.anno) IN
(
    SELECT p2.fixedId, MAX(p2.anno)
    FROM prodotto p2
    WHERE p.azienda_id = p2.azienda_id
    GROUP BY p2.fixedId
)

而且它只用了0.0256秒

好的SQL,干得漂亮。


6
有趣,您可以解释一下添加 GROUP BY 如何修复它吗? - cozos
15
子查询生成的临时表较小,因此执行速度更快,因为需要检查的数据较少。 - Sirmyself
6
我认为在第一个查询中,你在外部查询和子查询之间共享了变量,因此对于主查询中的每一行,子查询都会执行,但在第二个查询中,子查询仅执行一次,这样可以提高性能。 - Ali Faradjpour
2
Sql Server和MySql以及...Sql(除了NoSql)在基础架构上非常相似。我们有一种查询优化引擎,在其下面将IN(...)子句转换为连接(如果可能的话)。但是,当您在一个基于其基数进行索引的列上具有Group by时,它将会更快。因此,这真的取决于情况。 - AbbasAli Hashemian
2
你确定缓冲区已经清空了吗?如果你连续运行这两个查询,性能上会有很大的差异,这是非常合理的。 - Yuval Perelman
这并没有完全回答问题。如果它包括了使用JOIN重写的查询以及那个性能,那就是一个完整的答案。 - undefined

58

我预计第一个查询会更快,主要是因为它使用了等价和显式的JOIN。根据我的经验,IN 运算符非常慢,因为SQL通常将其作为一系列由"OR"分隔的WHERE子句来计算(WHERE x=Y OR x=Z OR...)。

然而,对于所有SQL操作,你的情况可能有所不同。速度将在很大程度上取决于索引(你是否在两个ID列上都建立了索引?这将非常有帮助...)等其他因素。

唯一真正确定哪个更快的方法是打开性能跟踪(IO统计特别有用)并运行它们两个。请确保在每次运行之间清除缓存!


21
我对这个答案有严重的怀疑,因为大多数DBMS(特别是SQL Server 2008及以后的版本)都将单个ID子查询(非关联的,即:不引用多个外部查询列)转换为相对较快的半连接。此外,如另一个答案中先前提到的,实际上第一个连接将为Dept中每个匹配ID的出现返回一行 - 这对于唯一的ID没有区别,但会给你在其他地方产生大量重复值。使用DISTINCT或GROUP BY来解决这些问题将导致更重的性能负载。请在SQL Server Management Studio中检查执行计划! - Erik Hart
4
IN子句作为OR的等效方式适用于参数/值列表,但不适用于子查询,因为子查询大多数情况下被视为连接操作。 - Erik Hart

18

性能基于你正在执行的数据量...

如果数据量较小,约 20k 左右,则 JOIN 更好。

如果数据量更大,比如 100k+,则 IN 更好。

如果你不需要来自另一张表的数据,则 IN 是不错的选择,但是最好选择 EXISTS。

我测试了所有这些标准并且表具有适当的索引。


11

开始查看执行计划,以查看SQL Server解释它们的方式有什么不同。您还可以使用Profiler实际运行查询多次并获得差异。

我不会预期这些差异会非常大,使用连接而不是子查询可以获得真正的大型性能提升的地方是在使用相关子查询时。

EXISTS通常比这两个更好,当您谈论左连接并希望左连接表中不存在的所有记录时,NOT EXISTS通常是更好的选择。


6

我知道这是一个旧帖子,但我认为这是一个非常重要的话题,特别是现在我们有超过1000万记录并且谈论到数据量达到了TB级别。

我也想分享一下我的观察。我的表格([data])中有大约4500万条记录,我的[cat]表中有约300条记录。对于所有我即将讨论的查询,我都进行了广泛的索引。

考虑示例1:

UPDATE d set category = c.categoryname
FROM [data] d
JOIN [cats] c on c.id = d.catid

对比例子2:

UPDATE d set category = (SELECT TOP(1) c.categoryname FROM [cats] c where c.id = d.catid)
FROM [data] d

示例1花费约23分钟运行。示例2需要大约5分钟。

因此,我可以得出结论,在这种情况下,子查询要快得多。当然,请记住,我正在使用M.2 SSD驱动器,可进行1GB / sec的i/o(即字节,而不是位),因此我的索引也非常快。因此,在您的情况下,速度可能也会受到影响。

如果这是一次性数据清理,则最好让其运行并完成。我使用TOP(10000)查看所需时间,然后乘以记录数量,然后再运行大型查询。

如果您正在优化生产数据库,则强烈建议预处理数据,即使用触发器或作业代理异步更新记录,以便实时访问检索静态数据。


6

性能应该是相同的;更重要的是在表上应用正确的索引和聚集,关于这个主题有一些好的资源可供参考。

(编辑以反映更新的问题)


5
这两个查询可能在语义上并不等价。如果一个员工在多个部门工作(在我工作的企业中是可能的;不过,这意味着你的表没有完全规范化),那么第一个查询将返回重复行,而第二个查询则不会。在这种情况下,为了使这两个查询等价,必须在SELECT子句中添加DISTINCT关键字,这可能会影响性能。
请注意,有一个设计原则指出,一张表应该模拟实体/类或实体/类之间的关系,而不应同时模拟两者。因此,我建议您创建第三个表,比如OrgChart,来模拟员工和部门之间的关系。

1
您可以使用“解释计划”来获得客观答案。 对于您的问题,存在过滤器可能会执行得最快。

3
“使用 Exists 过滤器可能会执行得最快”- 我认为可能不是这样,虽然要对实际数据进行测试才能得出确切答案。如果存在多个具有相同查找值的行,则使用 Exists 过滤器可能更快- 因此,如果查询正在检查是否已记录来自同一部门的其他员工,则 Exists 过滤器可能运行得更快,但在针对部门表进行查找时可能不是这样。 - user359040
1
在最后一种情况下,它会运行得更慢吗? - Snekse
1
这将取决于优化器的情况 - 在某些情况下可能会有所不同,但通常我预计性能会非常相似。 - user359040

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接