为什么 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 在从 utf8
到 latin1
字符集时忽略了索引 (finished.email
)。您还没有发布每一列的字符集和排序规则,所以我将使用 table 的默认字符集。排序规则必须兼容才能 MySQL 使用索引。
MySQL 可以强制(升级)一个 latin1
排序规则,这是非常有限的,最多可以达到 utf8
排序规则,例如 unicode_ci
(所以第一个查询可以通过将 latin1
排序规则升级到 utf8
来使用 saved.email
上的索引),但反之则不然(第二个查询不能使用 finished.email
上的索引,因为它可以不要将 utf8
归类降级为 latin1
).
解决方案是将两个电子邮件列更改为兼容的排序规则,也许最简单的方法是使它们具有相同的字符集和排序规则。
saved.email
和finished.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 在 email
列 CREATE TABLE
语句中发现了字符集的差异添加到您的问题。
我认为是字符集的差异导致 MySQL 无法为您的查询使用索引。
问题 2:如何使 LEFT JOIN 查询更快?
A: 我认为如果不更改模式(例如更改字符集),就不可能使特定查询更快 运行要匹配的两个电子邮件列中的一个。
"outer join" 到 finished
table 的唯一影响是只要找到多个匹配行就生成 "duplicate" 行。我不明白为什么需要外部连接。为什么不完全摆脱它,而是这样做:
SELECT saved.email FROM saved
我有两个查询,第一个(内连接)超快,第二个(左连接)超慢。如何使第二个查询更快?
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 在从 utf8
到 latin1
字符集时忽略了索引 (finished.email
)。您还没有发布每一列的字符集和排序规则,所以我将使用 table 的默认字符集。排序规则必须兼容才能 MySQL 使用索引。
MySQL 可以强制(升级)一个 latin1
排序规则,这是非常有限的,最多可以达到 utf8
排序规则,例如 unicode_ci
(所以第一个查询可以通过将 latin1
排序规则升级到 utf8
来使用 saved.email
上的索引),但反之则不然(第二个查询不能使用 finished.email
上的索引,因为它可以不要将 utf8
归类降级为 latin1
).
解决方案是将两个电子邮件列更改为兼容的排序规则,也许最简单的方法是使它们具有相同的字符集和排序规则。
saved.email
和finished.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 在 email
列 CREATE TABLE
语句中发现了字符集的差异添加到您的问题。
我认为是字符集的差异导致 MySQL 无法为您的查询使用索引。
问题 2:如何使 LEFT JOIN 查询更快?
A: 我认为如果不更改模式(例如更改字符集),就不可能使特定查询更快 运行要匹配的两个电子邮件列中的一个。
"outer join" 到 finished
table 的唯一影响是只要找到多个匹配行就生成 "duplicate" 行。我不明白为什么需要外部连接。为什么不完全摆脱它,而是这样做:
SELECT saved.email FROM saved