左连接优于内连接?

Posted

技术标签:

【中文标题】左连接优于内连接?【英文标题】:Left Join outperforming Inner Join? 【发布时间】:2010-09-16 05:02:50 【问题描述】:

我一直在分析我正在处理的应用程序中的一些查询,我遇到了一个查询,它检索的行数超过了必要的行数,结果集在应用程序代码中被缩减。

将 LEFT JOIN 更改为 INNER JOIN 会将结果集修剪为所需的内容,并且可能还会提高性能(因为选择的行更少)。实际上,LEFT JOIN'ed 查询的性能优于 INNER JOIN'ed,只需一半的时间即可完成。

LEFT JOIN:(总共 127 行,查询耗时 0.0011 秒)

INNER JOIN:(总共 10 行,查询耗时 0.0024 秒)

(我多次运行查询,这些都是平均值)。

在两者上运行 EXPLAIN 无法解释性能差异:

对于 INNER JOIN:

id  select_type     table   type    possible_keys   key     key_len     ref        rows     Extra
1   SIMPLE  contacts        index       NULL        name        302     NULL         235    Using where
1   SIMPLE  lists           eq_ref      PRIMARY     PRIMARY     4   contacts.list_id     1   
1   SIMPLE  lists_to_users  eq_ref      PRIMARY     PRIMARY     8   lists.id,const  1    
1   SIMPLE  tags            eq_ref      PRIMARY     PRIMARY     4   lists_to_users.tag_id   1    
1   SIMPLE  users           eq_ref      email_2     email_2     302     contacts.email 1    Using where

对于左连接:

id  select_type     table   type    possible_keys   key     key_len     ref     rows    Extra
1   SIMPLE          contacts index      NULL        name        302     NULL    235     Using where
1   SIMPLE        lists     eq_ref      PRIMARY     PRIMARY     4   contacts.list_id    1    
1   SIMPLE    lists_to_users eq_ref     PRIMARY     PRIMARY     8   lists.id,const  1    
1   SIMPLE         tags     eq_ref      PRIMARY     PRIMARY     4   lists_to_users.tag_id   1    
1   SIMPLE        users     eq_ref      email_2     email_2     302     contacts.email  1   

还有查询本身:

SELECT `contacts`.*, `lists`.`name` AS `group`, `lists`.`id` AS `group_id`, `lists`.`shared_yn`, `tags`.`name` AS `context`, `tags`.`id` AS `context_id`, `tags`.`color` AS `context_color`, `users`.`id` AS `user_id`, `users`.`avatar` 
FROM `contacts`  
LEFT JOIN `lists` ON lists.id=contacts.list_id  
LEFT JOIN `lists_to_users` ON lists_to_users.list_id=lists.id AND lists_to_users.user_id='1' AND lists_to_users.creator='1'  
LEFT JOIN `tags` ON tags.id=lists_to_users.tag_id 
INNER JOIN `users` ON users.email=contacts.email 
WHERE (contacts.user_id='1') 
ORDER BY `contacts`.`name` ASC

(我所说的子句是'users'表上的最后一个INNER JOIN)

查询在 mysql 5.1 数据库上运行,如果有影响的话。

有没有人知道为什么在这种情况下 LEFT JOIN 的查询优于 INNER JOIN 的查询?

更新:由于 Tomalak 建议我使用的小表使 INNER JOIN 更加复杂,我创建了一个包含一些模拟数据的测试数据库。 'users' 表是 5000 行,contacts 表是 ~500,000 行。结果是一样的(时间也没有改变,当您考虑到现在的表要大得多时,这令人惊讶)。

我还在联系人表上运行了 ANALYZE 和 OPTIMIZE。没有任何明显的区别。

【问题讨论】:

您是否尝试先放置内部连接? 我有,它确实将查询速度提高了 20%,但仍然比 LEFT JOIN 慢 尝试按顺序构建每个查询(加入一张表,测量,加入下一张等)也许这有助于您确定缓慢的操作。 问题不在于速度(仍然相当快),而在于几乎完全相似的查询的执行时间差异 - 更快的是 LEFT JOIN,这对我来说是莫名其妙的 是的,我已经看到它几乎没有什么不同。但是当你只是加入用户和联系人时,它仍然这样做吗?您有关于contacts.email 的索引吗? 【参考方案1】:

如果你认为 LEFT JOIN 的实现是 INNER JOIN + 更多的工作,那么这个结果是令人困惑的。如果 INNER JOIN 的实现是(LEFT JOIN + 过滤)呢?啊,现在清楚了。

在查询计划中,唯一的区别是:users... extra: using where。这意味着过滤。带有内部连接的查询中有一个额外的过滤步骤


这是一种不同于通常在 where 子句中使用的过滤。在 A 上创建索引以支持此过滤操作很简单。

SELECT *
FROM A
WHERE A.ID = 3

考虑这个查询:

SELECT *
FROM A
  LEFT JOIN B
  ON A.ID = B.ID
WHERE B.ID is not null

这个查询相当于内连接。 B 上没有索引可以帮助过滤操作。原因是 where 子句声明了连接结果的条件,而不是 B 上的条件。

【讨论】:

我知道左连接和内连接之间的区别。您可以对 WHERE 子句说同样的话,但是使用 where 子句过滤的查询通常需要更少的时间来计算。 我阅读了您添加的内容,虽然我认为您可能会通过额外的过滤步骤进行某些操作,但我认为您没有找到原因。额外的过滤列“电子邮件”(已使用)上有一个索引,因此它应该足够快以提高性能。 是的,电子邮件上的索引确实有助于左连接。不,电子邮件上的索引不允许快速过滤加入后的结果。【参考方案2】:

这可能是由于 INNER JOIN 必须检查两个表中的每一行以查看列值(在您的情况下为电子邮件)是否匹配。 LEFT JOIN 无论如何都会从一张表中返回所有内容。如果它被编入索引,那么它也会知道该做什么更快。

【讨论】:

我尝试在 email 列上使用索引,在 name + email 列上使用组合索引,但查询执行计划保持不变 我猜这将有助于 INNER 和 LEFT 连接,所以我没想到这样做会比另一个更快。 内连接扫描一个表并在另一个表中找到匹配的行,理想情况下使用和索引。它不必按照您的建议检查两个表中的每一行。 扫描不就是这样吗?为什么会有性能差异。【参考方案3】:

表基数对查询优化器有影响。我猜小表,因为你有使内部连接更复杂的操作。一旦您的记录多于数据库服务器愿意保留在内存中的记录,内连接可能会开始优于左连接。

【讨论】:

这很有趣。我必须检查一个更大的集合,看看它是否像您描述的那样执行。 我用更大的表重新运行,结果是一样的。 +1 on answer .@Eran Galperin 我已经阅读了你关于你的问题的笔记,你谈论的那些表格根本不是“大”的。使用今天的硬件,当我们谈论大表时,您需要数百万行的表。【参考方案4】:

imo 您正在陷入被称为过早优化的陷阱。查询优化器是非常变化无常的东西。我的建议是继续前进,直到您确定某个特定连接存在问题为止。

【讨论】:

这不是关于优化,而是关于理解为什么查询会以某种方式运行。【参考方案5】:

试试这个:

SELECT `contacts`.*, `lists`.`name` AS `group`, `lists`.`id` AS `group_id`, `lists`.`shared_yn`, `tags`.`name` AS `context`, `tags`.`id` AS `context_id`, `tags`.`color` AS `context_color`, `users`.`id` AS `user_id`, `users`.`avatar` 
FROM `contacts`  
INNER JOIN `users` ON contacts.user_id='1' AND users.email=contacts.email
LEFT JOIN `lists` ON lists.id=contacts.list_id  
LEFT JOIN `lists_to_users` ON lists_to_users.user_id='1' AND lists_to_users.creator='1' AND lists_to_users.list_id=lists.id
LEFT JOIN `tags` ON tags.id=lists_to_users.tag_id 
ORDER BY `contacts`.`name` ASC

这应该会给您带来额外的性能,因为:

您将所有内连接放在出现任何“左”或“右”连接之前。这会在应用后续外连接之前过滤掉一些记录 “AND”运算符的短路(“AND”的顺序很重要)。如果列和字面值的比较为假,则不会执行所需的表扫描以查找表 PK 和 FK 之间的比较

如果您没有发现任何性能改进,则将所有列集替换为“COUNT(*)”并进行左侧/内部测试。这样,无论查询如何,您都将仅检索 1 个单行和 1 个单列(计数),因此您可以放弃返回的字节数是导致查询缓慢的原因:

SELECT COUNT(*)
FROM `contacts`  
INNER JOIN `users` ON contacts.user_id='1' AND users.email=contacts.email
LEFT JOIN `lists` ON lists.id=contacts.list_id  
LEFT JOIN `lists_to_users` ON lists_to_users.user_id='1' AND lists_to_users.creator='1' AND lists_to_users.list_id=lists.id
LEFT JOIN `tags` ON tags.id=lists_to_users.tag_id 

祝你好运

【讨论】:

【参考方案6】:

LEFT JOIN 返回的行数比 INNER JOIN 多,因为这两个不同。 如果 LEFT JOIN 在它正在查找的表中没有找到相关条目,它将为该表返回 NULL。 但如果 INNER JOIN 没有找到相关条目,则根本不会返回整个行。

但是对于您的问题,您是否启用了 query_cache ? 尝试使用

运行查询
SELECT SQL_NO_CACHE `contacts`.*, ...

除此之外,我会用更多数据填充表格,然后运行

ANALYZE TABLE t1, t2;
OPTIMIZE TABLE t1, t2;

看看会发生什么。

【讨论】:

当然左连接返回更多行,这不是问题的重点。为什么它运行得更快而返回更多行让我感到困惑

以上是关于左连接优于内连接?的主要内容,如果未能解决你的问题,请参考以下文章

SQL中的左连接与右连接,内连接有啥区别

如果查询中包含内连接,为啥左连接会变成内连接?

sql左连接 右连接 内连接 外连接都是啥

MySQL连接查询之内连接左连接右连接自连接

Linq 和 SQL的左连接右连接内链接

MySql连接——内连接外连接(左连接右连接全连接)