问题 为什么mySQL查询,左连接'相当'比我的内连接快


我研究了这个,但我仍然无法解释原因:

SELECT cl.`cl_boolean`, l.`l_name`
FROM `card_legality` cl
INNER JOIN `legality` l ON l.`legality_id` = cl.`legality_id`
WHERE cl.`card_id` = 23155

明显慢于:

SELECT cl.`cl_boolean`, l.`l_name`
FROM `card_legality` cl
LEFT JOIN `legality` l ON l.`legality_id` = cl.`legality_id`
WHERE cl.`card_id` = 23155

115毫秒对478毫秒。他们都使用InnoDB并且定义了关系。 'card_legality'包含大约200k行,而'legality'表包含11行。以下是每个的结构:

CREATE TABLE `card_legality` (
  `card_id` varchar(8) NOT NULL DEFAULT '',
  `legality_id` int(3) NOT NULL,
  `cl_boolean` tinyint(1) NOT NULL,
  PRIMARY KEY (`card_id`,`legality_id`),
  KEY `legality_id` (`legality_id`),
  CONSTRAINT `card_legality_ibfk_2` FOREIGN KEY (`legality_id`) REFERENCES `legality` (`legality_id`),
  CONSTRAINT `card_legality_ibfk_1` FOREIGN KEY (`card_id`) REFERENCES `card` (`card_id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

和:

CREATE TABLE `legality` (
  `legality_id` int(3) NOT NULL AUTO_INCREMENT,
  `l_name` varchar(16) NOT NULL DEFAULT '',
  PRIMARY KEY (`legality_id`)
) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=latin1;

我可以简单地使用LEFT-JOIN,但它似乎不太正确......请问有什么想法吗?

更新: 根据要求,我已经包括每个解释的结果。我以前跑了它,但我不假装对它有透彻的了解..

id  select_type table   type    possible_keys   key key_len ref rows    Extra
1   SIMPLE  cl  ALL PRIMARY NULL    NULL    NULL    199747  Using where
1   SIMPLE  l   eq_ref  PRIMARY PRIMARY 4   hexproof.co.uk.cl.legality_id   1   

AND,内连接:

id  select_type table   type    possible_keys   key key_len         ref                         rows    Extra
1   SIMPLE  l   ALL PRIMARY NULL    NULL    NULL    11  
1   SIMPLE  cl  ref PRIMARY,legality_id legality_id 4   hexproof.co.uk.l.legality_id    33799   Using where

11017
2017-11-21 20:19


起源

顺便一提 card_id 是一个VARCHAR因为我别无选择,我通常不会接受。 - Ben


答案:


这是因为card_id上的varchar。 MySQL不能将card_id上的索引用作card_id,如此处所述 mysql类型转换。重要的是

为了比较字符串列和数字,MySQL不能使用   列上的索引可以快速查找值。如果str_col是   索引字符串列,执行时不能使用索引   在以下语句中查找:

SELECT * FROM tbl_name WHERE str_col = 1;

这样做的原因是可能存在许多不同的字符串   转换为值1,例如“1”,“1”或“1a”。

如果您将查询更改为

SELECT cl.`cl_boolean`, l.`l_name`
FROM `card_legality` cl
INNER JOIN `legality` l ON l.`legality_id` = cl.`legality_id`
WHERE cl.`card_id` = '23155'

SELECT cl.`cl_boolean`, l.`l_name`
FROM `card_legality` cl
LEFT JOIN `legality` l ON l.`legality_id` = cl.`legality_id`
WHERE cl.`card_id` = '23155'

您应该看到速度的巨大提升,并且看到不同的EXPLAIN。

以下是一个类似(但更简单)的测试来显示:

> desc id_test;
+-------+------------+------+-----+---------+-------+
| Field | Type       | Null | Key | Default | Extra |
+-------+------------+------+-----+---------+-------+
| id    | varchar(8) | NO   | PRI | NULL    |       |
+-------+------------+------+-----+---------+-------+
1 row in set (0.17 sec)

> select * from id_test;
+----+
| id |
+----+
| 1  |
| 2  |
| 3  |
| 4  |
| 5  |
| 6  |
| 7  |
| 8  |
| 9  |
+----+
9 rows in set (0.00 sec)

> explain select * from id_test where id = 1;
+----+-------------+---------+-------+---------------+---------+---------+------+------+--------------------------+
| id | select_type | table   | type  | possible_keys | key     | key_len | ref  | rows | Extra                    |
+----+-------------+---------+-------+---------------+---------+---------+------+------+--------------------------+
|  1 | SIMPLE      | id_test | index | PRIMARY       | PRIMARY | 10      | NULL |    9 | Using where; Using index |
+----+-------------+---------+-------+---------------+---------+---------+------+------+--------------------------+
1 row in set (0.00 sec)


> explain select * from id_test where id = '1';
+----+-------------+---------+-------+---------------+---------+---------+-------+------+-------------+
| id | select_type | table   | type  | possible_keys | key     | key_len | ref   | rows | Extra       |
+----+-------------+---------+-------+---------------+---------+---------+-------+------+-------------+
|  1 | SIMPLE      | id_test | const | PRIMARY       | PRIMARY | 10      | const |    1 | Using index |
+----+-------------+---------+-------+---------------+---------+---------+-------+------+-------------+
1 row in set (0.00 sec)

在第一种情况下有 Using where; Using index 第二是 Using index。也是参考 NULL 要么 CONST。不用说,第二个更好。


9
2017-11-21 20:44



啊哈!辉煌!你是正确的,在'card_id'上搜索一个INT,实际上它是一个VARCHAR使得这些查询比它们应该的速度慢近600倍!谢谢安德烈亚斯:) - Ben
是啊。非常好的信息在这里感谢Andreas。 - stefgosselin
我添加了一个解释这个问题的链接。这是很好的阅读。 - Andreas Wederbrand


虽然我怀疑它可能是因为用于card_id的varchar类型,所以L2G总结了它。

我实际打印出来了 这个信息丰富的页面 用于基准测试/分析快速。这是一个快速的穷人分析技术:

Time a SQL on MySQL
Enable Profiling
mysql> SET PROFILING = 1
...
RUN your SQLs
...
mysql> SHOW PROFILES;

+----------+------------+-----------------------+
| Query_ID | Duration   | Query                 |
+----------+------------+-----------------------+
|        1 | 0.00014600 | SELECT DATABASE()     |
|        2 | 0.00024250 | select user from user |
+----------+------------+-----------------------+
mysql> SHOW PROFILE for QUERY 2;

+--------------------------------+----------+
| Status                         | Duration |
+--------------------------------+----------+
| starting                       | 0.000034 |
| checking query cache for query | 0.000033 |
| checking permissions           | 0.000006 |
| Opening tables                 | 0.000011 |
| init                           | 0.000013 |
| optimizing                     | 0.000004 |
| executing                      | 0.000011 |
| end                            | 0.000004 |
| query end                      | 0.000002 |
| freeing items                  | 0.000026 |
| logging slow query             | 0.000002 |
| cleaning up                    | 0.000003 |
+--------------------------------+----------+

祝你好运,哦,请发表你的发现!


3
2017-11-21 20:38



这是stefgosselin非常有用的信息,谢谢。你的预后实际上是正确的,但安德烈亚斯的解释解释了它。谢谢你的帮助 :) - Ben


我试试 说明 在这两个查询上。只需在每个前缀 SELECT 同 EXPLAIN 并运行它们。它提供了有关mySQL如何优化和执行查询的非常有用的信息。


3
2017-11-21 20:26



根据手头的主题,我会说OP很有可能知道如何使用“EXPLAIN”。无论哪种方式,这种信息都应该放在评论中,而不是在答案中,因为你没有试图回答他的问题。 - Naatan
嗨L2G,谢谢你的评论。我会坚持下去 说明 功能,看看我能找到什么。 mysql手册有点缺乏(或者至少对我来说)。非常感谢 - Ben


我很确定MySql对左连接有更好的优化 - 目前还没有证据支持这一点。

ETA:一个快速的侦察轮,我找不到任何具体的东西来维护我的观点......


0
2017-11-21 20:28



谢谢K.Bob,我读的类似,但和你一样;没有证据。 - Ben