我一直在处理我正在处理的应用程序中的一些查询,并且遇到了一个检索更多行而不是必要的查询,结果集在应用程序代码中被删除。
将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
LEFT 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
查询本身:
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。没有任何明显的区别。
答案 0 :(得分:12)
如果您认为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上的条件。
答案 1 :(得分:6)
这可能是由于INNER JOIN必须检查两个表中的每一行,以查看列值(在您的情况下为电子邮件)是否匹配。无论如何,LEFT JOIN都将从一个表中返回。如果它已编入索引,那么它也会更快地知道该做什么。
答案 2 :(得分:4)
表基数对查询优化器有影响。我猜小桌子就像你使内连接更复杂的操作。只要您有比DB服务器愿意保留在内存中的记录更多的记录,内连接可能会开始优于左连接。
答案 3 :(得分:2)
imo你正陷入被称为过早优化的陷阱。查询优化器是疯狂的变化无常的东西。我的建议是继续进行,直到你能确定特定连接是否有问题为止。
答案 4 :(得分: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
这应该会给你额外的表现,因为:
如果没有找到任何性能改进,则将所有列集替换为“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
祝你好运
答案 5 :(得分:-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;
看看会发生什么。