15

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

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

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

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

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

在两者上运行 EXPLAIN 不会显示任何解释性能差异的信息:

对于内部联接:

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

(我正在谈论的子句是“用户”表上的最后一个 INNER JOIN)

该查询在 MySQL 5.1 数据库上运行,如果它有所作为的话。

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

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

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

4

6 回答 6

12

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

在查询计划中,唯一的区别是:用户...额外:使用 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 上的条件。

于 2008-10-09T18:20:17.650 回答
6

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

于 2008-10-09T06:26:22.523 回答
4

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

于 2008-10-09T06:41:45.673 回答
2

imo您陷入了被称为过早优化的陷阱。查询优化器是极其善变的东西。我的建议是继续前进,直到您可以确定某个特定的连接存在问题。

于 2008-10-09T07:32:24.247 回答
0

尝试这个:

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 

祝你好运

于 2009-04-22T19:52:37.057 回答
-3

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

但是对于您的问题,您是否启用了 query_cache ?尝试运行查询

SELECT SQL_NO_CACHE `contacts`.*, ...

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

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

看看会发生什么。

于 2008-10-09T09:23:08.623 回答