编码内部联接的两种方法中哪一种更快?
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 的技巧之一。
【讨论】:
以上是关于编码内部联接的两种方法中哪一种更快?的主要内容,如果未能解决你的问题,请参考以下文章