左连接优于内连接?
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;
看看会发生什么。
【讨论】:
当然左连接返回更多行,这不是问题的重点。为什么它运行得更快而返回更多行让我感到困惑以上是关于左连接优于内连接?的主要内容,如果未能解决你的问题,请参考以下文章