为什么LEFT JOIN比INNER JOIN慢?

时间:2015-01-05 20:21:28

标签: mysql join query-optimization

我有两个查询,第一个(内连接)超快,第二个(左连接)超慢。如何快速进行第二次查询?

EXPLAIN SELECT saved.email FROM saved INNER JOIN finished ON finished.email = saved.email;

id  select_type table   type    possible_keys   key key_len ref rows    Extra
1   SIMPLE  finished    index   NULL    email   258 NULL    32168   Using index
1   SIMPLE  saved   ref email   email   383 func    1   Using where; Using index

EXPLAIN SELECT saved.email FROM saved LEFT JOIN finished ON finished.email = saved.email;

id  select_type table   type    possible_keys   key key_len ref rows    Extra
1   SIMPLE  saved   index   NULL    email   383 NULL    40971   Using index
1   SIMPLE  finishedindex   NULL    email   258 NULL    32168   Using index

编辑:我已在下面的两个表中添加了表格信息。

CREATE TABLE `saved` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `slug` varchar(255) DEFAULT NULL,
  `email` varchar(127) NOT NULL,
  [omitted fields include varchar, text, longtext, int],
  PRIMARY KEY (`id`),
  KEY `slug` (`slug`),
  KEY `email` (`email`)
) ENGINE=MyISAM AUTO_INCREMENT=56329 DEFAULT CHARSET=utf8;

CREATE TABLE `finished` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `slug` varchar(255) DEFAULT NULL,
  `submitted` int(11) DEFAULT NULL,
  `status` int(1) DEFAULT '0',
  `name` varchar(255) DEFAULT NULL,
  `email` varchar(255) DEFAULT NULL,
  [omitted fields include varchar, text, longtext, int],
  PRIMARY KEY (`id`),
  KEY `assigned_user_id` (`assigned_user_id`),
  KEY `event_id` (`event_id`),
  KEY `slug` (`slug`),
  KEY `email` (`email`),
  KEY `city_id` (`city_id`),
  KEY `status` (`status`),
  KEY `recommend` (`recommend`),
  KEY `pending_user_id` (`pending_user_id`),
  KEY `submitted` (`submitted`)
) ENGINE=MyISAM AUTO_INCREMENT=33063 DEFAULT CHARSET=latin1;

4 个答案:

答案 0 :(得分:9)

使用INNER JOIN,MySQL通常会从具有最小行数的表开始。在这种情况下,它从表finished开始,并使用saved上的索引在saved.email中查找相应的记录。

对于LEFT JOIN,(不包括一些优化)MySQL通常按顺序连接记录(从最左边的表开始)。在这种情况下,MySQL以表saved开头,然后尝试在finished中查找每个相应的记录。由于finished.email上没有可用索引,因此必须对每次查找执行完整扫描。

修改

现在您发布了模式,我可以看到MySQL在从finished.emailutf8字符集时忽略了索引(latin1)。您还没有为每列发布字符集和排序规则,因此我按照表的默认字符集进行操作。排序规则必须兼容才能使MySQL使用索引。

MySQL可以强制(升级)latin1归类,这种归类非常有限,最高可达utf8归类,例如unicode_ci(因此第一个查询可以使用saved.email上的索引1}}将latin1归类升级到utf8),但相反的情况则不然(第二个查询无法使用finished.email上的索引,因为它不能将utf8归类降级为latin1)。

解决方案是将两个电子邮件列更改为兼容的排序规则,最简单的方法是将它们设置为相同的字符集和排序规则。

答案 1 :(得分:8)

LEFT JOIN查询而不是INNER JOIN查询,因为它做更多工作

从EXPLAIN输出看,MySQL看起来像是在进行嵌套循环连接。 (嵌套循环没有问题;我认为这是MySQL在5.5及更早版本中使用的唯一连接操作。)

对于INNER JOIN查询,MySQL正在使用高效的 "ref" (索引查找)操作来查找匹配的行。

但对于LEFT JOIN查询,看起来MySQL正在对索引执行完整扫描以查找匹配的行。因此,通过嵌套循环连接操作,MySQL正在对另一个表中的每一行进行完整的索引扫描扫描。因此,这是大约数万次扫描的顺序,每次扫描都在检查数万行。

使用EXPLAIN输出中的估计行数,这将需要(40971 * 32168 =)1,317,955,128字符串比较。

INNER JOIN查询避免了大量工作,因此更快。 (它通过使用索引操作来避免所有这些字符串比较。

-- LEFT JOIN
id select table    type   key   key_len ref    rows  Extra
-- ------ -------- -----  ----- ------- ----  -----  ------------------------
1  SIMPLE saved    index  email     383 NULL  40971  Using index
1  SIMPLE finished index  email     258 NULL  32168  Using index

-- INNER JOIN 
id select table    type   key   key_len ref    rows  Extra
-- ------ -------- -----  ----- ------- ----  -----  ------------------------  
1  SIMPLE finished index  email     258 NULL  32168  Using index
1  SIMPLE saved    ref    email     383 func      1  Using where; Using index
                   ^^^^^                ^^^^  ^^^^^  ^^^^^^^^^^^^

注意: Markus Adams发现了添加到您问题的email CREATE TABLE 语句中字符集的差异。

我认为正是字符集中的差异阻止了MySQL为您的查询使用索引。


Q2:如何更快地进行LEFT JOIN查询?

答:我认为,如果不更改架构,可以让特定查询更快地运行,例如更改两个电子邮件列的字符集以匹配。< / p>

finished表的“外部联接”的唯一影响就是在找到多个匹配行时生成“重复”行。我不明白为什么需要外连接。为什么不完全摆脱它,只是做:

SELECT saved.email FROM saved

答案 2 :(得分:2)

我担心可能需要更多信息。

但是,inner joins会消除任何具有空外键的项目(如果愿意,则不匹配)。这意味着要扫描以关联的行数较少。

但是对于left join,任何不匹配都需要给出一个空行,因此无论如何都会扫描所有行 - 没有什么可以消除。

这使数据集更大,需要更多资源来处理。此外,当您编写select时,请不要select * - 而是明确说明您想要的列。

答案 3 :(得分:1)

saved.emailfinished.email的数据类型在两个方面有所不同。首先,它们有不同的长度。其次,finished.email可以为NULL。因此,您的LEFT JOIN操作无法利用finished.email上的索引。

您可以将finished.email的定义更改为此,以便它与您加入的字段相匹配吗?

`email` varchar(127) NOT NULL

如果你这样做,你可能会获得加速。