为什么 LEFT JOIN 比 INNER JOIN 慢?

Why is LEFT JOIN slower than INNER JOIN?

我有两个查询,第一个(内连接)超快,第二个(左连接)超慢。如何使第二个查询更快?

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

编辑:我在下面为两个 table 添加了 table 信息。

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;

恐怕可能需要更多信息。

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

然而,对于 left join,任何不匹配项都需要给定一个空白行,因此无论如何都会扫描所有行 - 无法消除任何内容。

这使得数据集更大,需要更多资源来处理。另外,当你写 select 时,不要写 select * -- 相反,明确说明你想要的列。

对于 INNER JOIN,MySQL 通常会从行数最少的 table 开始。在这种情况下,它以 table finished 开始,并使用 saved.email.

上的索引在 saved 中查找相应的记录

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

编辑

现在您发布了架构,我可以看到 MySQL 在从 utf8latin1 字符集时忽略了索引 (finished.email)。您还没有发布每一列的字符集和排序规则,所以我将使用 table 的默认字符集。排序规则必须兼容才能 MySQL 使用索引。

MySQL 可以强制(升级)一个 latin1 排序规则,这是非常有限的,最多可以达到 utf8 排序规则,例如 unicode_ci(所以第一个查询可以通过将 latin1 排序规则升级到 utf8 来使用 saved.email 上的索引),但反之则不然(第二个查询不能使用 finished.email 上的索引,因为它可以不要将 utf8 归类降级为 latin1).

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

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

您能否将 finished.email 的定义更改为此,使其与您加入的字段相匹配?

`email` varchar(127) NOT NULL

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

LEFT JOIN 查询比 INNER JOIN 查询,因为它做更多的工作

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

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

但是对于 LEFT JOIN 查询,MySQL 似乎正在对索引进行 全面扫描 以查找匹配的行。因此,通过嵌套循环连接操作,MySQL 正在对来自其他 table 的 每一行 执行完整索引扫描。所以,这大约是数万次扫描,每一次扫描都在检查数万行。

使用 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 在 emailCREATE TABLE 语句中发现了字符集的差异添加到您的问题。

我认为是字符集的差异导致 MySQL 无法为您的查询使用索引。


问题 2:如何使 LEFT JOIN 查询更快?

A: 我认为如果不更改模式(例如更改字符集),就不可能使特定查询更快 运行要匹配的两个电子邮件列中的一个。

"outer join" 到 finished table 的唯一影响是只要找到多个匹配行就生成 "duplicate" 行。我不明白为什么需要外部连接。为什么不完全摆脱它,而是这样做:

SELECT saved.email FROM saved