我有两个查询,第一个(内连接)超快,第二个(左连接)超慢。如何快速进行第二次查询?
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;
答案 0 :(得分:9)
使用INNER JOIN,MySQL通常会从具有最小行数的表开始。在这种情况下,它从表finished
开始,并使用saved
上的索引在saved.email
中查找相应的记录。
对于LEFT JOIN,(不包括一些优化)MySQL通常按顺序连接记录(从最左边的表开始)。在这种情况下,MySQL以表saved
开头,然后尝试在finished
中查找每个相应的记录。由于finished.email
上没有可用索引,因此必须对每次查找执行完整扫描。
修改强>
现在您发布了模式,我可以看到MySQL在从finished.email
到utf8
字符集时忽略了索引(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.email
和finished.email
的数据类型在两个方面有所不同。首先,它们有不同的长度。其次,finished.email
可以为NULL。因此,您的LEFT JOIN
操作无法利用finished.email
上的索引。
您可以将finished.email
的定义更改为此,以便它与您加入的字段相匹配吗?
`email` varchar(127) NOT NULL
如果你这样做,你可能会获得加速。