编码内部联接的两种方法中哪一种更快?

Posted

技术标签:

【中文标题】编码内部联接的两种方法中哪一种更快?【英文标题】:Which of two ways of coding an Inner join is faster? 【发布时间】:2010-11-09 04:07:17 【问题描述】:

我更喜欢在 t-sql 中使用实际上是内联连接的代码,而不是在存储过程或视图的末尾有一个长长的连接列表。

例如,我编码:

SELECT      PKey  ,    Billable, 
    (SELECT LastName  FROM Contact.dbo.Contacts WHERE (Pkey = Contacts_PKey)),
    (SELECT Description FROM Common.dbo.LMain WHERE (PKey= DType)),  
    (SELECT  TaskName  FROM  Common.dbo.LTask WHERE  (PKey =  TaskType)) ,  
    StartTime,  EndTime,  SavedTime
FROM   dbo.TopicLog   where  StartTime > '7/9/09'  ORDER BY  StartTime

而不是

SELECT t.PKey, t.Billable, c.LastName, m.Description, lt.TaskName, t.StartTime, t.EndTime, t.SavedTime
FROM dbo.TopicLog AS t     
inner join  Contact.dbo.Contacts as c   on  c.Pkey = t.Contacts_PKey and t.StartTime > '7/9/09'
inner join  Common.dbo.LMain  as m  on  m.PKey = t.DType
inner join  Common.dbo.LTask  as lt on lt.PKey = t.TaskType
ORDER BY t.StartTime

我更喜欢这种类型的语法,因为它在编写或调试时不太容易混淆,尤其是当有许多表被连接或其他事情发生时(case 语句、t-sql 函数、自连接等)

但我的问题是 - 以这种方式查询数据库会影响性能。

我还没有收集到足够的数据来衡量差异,但我会在以后的某个时候。

我想在继续之前先了解一下。我不想稍后再回去重新编码以提高性能。

【问题讨论】:

【参考方案1】:

通常是第二个(实际的内部连接)。第一个(子查询)对每一行执行 3 次查询,但这通常由编译器管理,以减少差异。

最好的:Check the query execution plans 为你自己!

由于您的性能下降,我的猜测是您的表没有正确索引。您应该在所有主键上有聚集索引,在外键(用于连接的那些)上有非聚集索引。

我应该注意,当且仅当您在所有连接条件中都有匹配的值时,这两个查询是等效的(即,始终返回主表中的所有行)。否则,如果没有匹配项,您将从子查询中获得 null。内连接主动过滤掉任何与连接条件不匹配的行。子查询方法实际上等同于左外连接(在结果上,而不是速度或执行上)。

【讨论】:

+1。正如您所指出的,深思熟虑的索引所带来的胜利更有可能产生显着的收益。 (但检查执行计划会让他们确定!) +1 "自己检查查询执行计划!"这是唯一确定的方法。优化器可能自动为您将它们变成 JOIN。虽然,这两个查询并不完全相同。 #1 是左连接,#2 是内连接。所以他们无论如何都会给你不同的计划。 这非常具有误导性 - 这是一个常见的误解,即由于您给出的原因子查询较慢,实际上 SQL 服务器在重新编译期间尽可能将子查询重写为连接。 @kragen:请阅读我的全部条目。具体来说,“但这一般由编译器管理”和“检查查询执行计划”。另见海滩的评论。这已得到解决,至少没有被误解。【参考方案2】:

第一种方法根本不是内连接,它是一个相关子查询。 而且它们更像是左外连接而不是内连接,因为当没有匹配值时它们将返回 NULL。

【讨论】:

【参考方案3】:

第一个看起来像是一种病态的方式来加入我。我会避免它,如果没有其他原因,它是不寻常的 - 一个有经验的 SQL DBA 看着它来维护它会花一段时间寻找它为什么这样编码的原因,而就你而言没有真正的原因想要查询做。如果缺少数据,它的行为更像是外连接。

第二个例子看起来很正常。

您应该知道进行内部连接的老式方法是这样的:

SELECT t.PKey, t.Billable, 
 c.LastName, m.Description, lt.TaskName, 
 t.StartTime, t.EndTime, t.SavedTime
FROM 
 dbo.TopicLog as t, Contact.dbo.Contacts as c, 
 Common.dbo.LMain as m,  Common.dbo.LTask as lt   
WHERE c.Pkey = t.Contacts_PKey and t.StartTime > '7/9/09'
  AND m.PKey = t.DType
  AND lt.PKey = t.TaskType
ORDER BY  t.StartTime

猜测这相当于现代的“inner join table on field”语法一旦被解析。

正如另一个答案所说,如果您正在寻找更快的查询,首先要做的是检查表的索引是否已排序。然后看查询执行计划。

【讨论】:

看来这种语法是他所追求的。即使是一个小表(比如可能超过 4000 行),为每个被选中的行表执行子查询的索引或没有索引都会很慢。【参考方案4】:

OP 中的两个查询说的非常不同,并且只有在正确的数据模型假设到位时才会产生相同的结果:

    查找中使用的每一列都没有空约束和外键约束。

    使用了查找表的主键或唯一键。

在 OP 特定情况下,这些假设可能是正确的,但在一般情况下,这些假设是不同的。

正如其他人指出的那样,子查询更像是一个外连接,因为它会为 LastName、Description 和 Taskname 列返回 null,而不是完全过滤掉该行。

另外,如果其中一个子查询返回多于一行,就会报错。

就个人喜好而言,我更喜欢使用连接语法的第二个示例,但这是主观的。

【讨论】:

【参考方案5】:

一般来说,简单子查询与连接的性能没有区别 - 一个常见的误解是子查询要慢得多(因为 SQL 服务器必须循环遍历内部查询),但通常说这是完全不正确!在编译过程中,SQL 服务器会生成一个执行树,并且在这些树中,子查询通常相当于连接。

值得注意的是,您的两个查询在逻辑上并不相同,并且对我产生了不同的结果,第二个查询实际上应该读到以下内容:(这仍然不相同,但更接近)

SELECT t.PKey, t.Billable, c.LastName, m.Description, lt.TaskName, t.StartTime, t.EndTime, t.SavedTime
FROM dbo.TopicLog AS t     
LEFT OUTER JOIN Contact.dbo.Contacts as c   on  c.Pkey = t.Contacts_PKey
LEFT OUTER JOIN Common.dbo.LMain  as m  on  m.PKey = t.DType
LEFT OUTER JOIN Common.dbo.LTask  as lt on lt.PKey = t.TaskType
WHERE t.StartTime > '7/9/09'
ORDER BY t.StartTime

在我的测试中,子查询生成了一个执行计划,其读取次数大大减少(15 次而不是 1000 次),但 cpu 略高 - 平均而言,执行时间大致相等。

值得注意的是,情况并非总是如此(尤其是在评估子查询中的函数时),并且有时您可能会因子查询而遇到问题。不过一般来说,最好只在遇到性能问题时才担心这种情况。

【讨论】:

【参考方案6】:

一般而言,子查询(即第一个示例)速度较慢,但​​优化和分析查询的最简单方法是通过您的特定数据库尝试它们,MS SQL Server 提供了出色的分析和性能调整工具。

【讨论】:

这完全不正确 - 通常 SQL 服务器将子查询解析为与连接生成的执行树相同的执行树。【参考方案7】:

许多 SQL 程序员完全不知道优化器经常将子查询解析为连接。两个查询中的性能问题都可能没有原因。

查看执行计划!

【讨论】:

【参考方案8】:

我认为第二个执行得更快。 这背后的原因是通过在您的示例中使用别名(t,c,m 等)名称关系引擎可以轻松找到指向表位置的指针。

我认为这是 sql tunning 的技巧之一。

【讨论】:

以上是关于编码内部联接的两种方法中哪一种更快?的主要内容,如果未能解决你的问题,请参考以下文章

在 VBA 中执行存储过程的两种方法,哪一种更好?

关于匿名内部类实现的两种方法

从 S3 加载 spark DF,多个文件。这些方法中哪一种最好?

Java线程Thread使用匿名内部类创建的两种方式

每个连接的线程与每个请求的线程有啥区别?

目前最受客户欢迎的两种“上网卡”,不知道你现在用的哪一种?