all*_*hou 5 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
Run Code Online (Sandbox Code Playgroud)
编辑:我已在下面的两个表中添加了表格信息.
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;
Run Code Online (Sandbox Code Playgroud)
使用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通过升级latin1排序规则来使用索引utf8),但相反的情况并非如此(第二个查询不能使用)索引finished.email因为它无法将utf8排序规则降级到latin1).
解决方案是将两个电子邮件列更改为兼容的排序规则,最简单的方法是将它们设置为相同的字符集和排序规则.
该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
^^^^^ ^^^^ ^^^^^ ^^^^^^^^^^^^
Run Code Online (Sandbox Code Playgroud)
注意: Markus Adams 发现了添加到您问题的email列CREATE TABLE语句中字符集的差异.
我相信,正在阻止MySQL为您的查询使用索引的字符集中的差异.
Q2:如何更快地进行LEFT JOIN查询?
答:我不相信可以让特定的查询更快地运行,而无需更改架构,例如更改两个电子邮件列的字符集以匹配.
对finished表的"外部联接" 看起来唯一的影响就是在找到多个匹配行时生成"重复"行.我不明白为什么需要外连接.为什么不完全摆脱它,只是做:
SELECT saved.email FROM saved
Run Code Online (Sandbox Code Playgroud)