为啥 mySQL 查询,左连接“明显”比我的内连接快
Posted
技术标签:
【中文标题】为啥 mySQL 查询,左连接“明显”比我的内连接快【英文标题】:Why is mySQL query, left join 'considerably' faster than my inner join为什么 mySQL 查询,左连接“明显”比我的内连接快 【发布时间】:2012-01-03 07:37:13 【问题描述】:我对此进行了研究,但我仍然无法解释原因:
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
【问题讨论】:
顺便说一句,card_id
是一个 VARCHAR,因为我别无选择,我通常不会接受。
【参考方案1】:
我会在这两个查询中尝试EXPLAIN。只需在每个 SELECT
前面加上 EXPLAIN
并运行它们。它提供了关于 mysql 如何优化和执行查询的非常有用的信息。
【讨论】:
根据手头的主题,我想说 OP 很有可能知道如何使用“EXPLAIN”。无论哪种方式,这种信息都应该放在评论中,而不是放在答案中,因为你没有试图回答他的问题。 您好 L2G,感谢您的评论。我会坚持使用 EXPLAIN 功能,看看我能找到什么。 mysql 手册有些欠缺(或者至少对我来说是这样)。非常感谢【参考方案2】:这是因为card_id 上的varchar。 MySQL 不能将 card_id 上的索引用作 card_id,如此处所述mysql type conversion。重要的部分是
对于字符串列与数字的比较,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'
您应该会看到速度的巨大提升,并且还会看到不同的解释。
这里有一个类似(但更简单)的测试来证明这一点:
> 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
。 ref 也是NULL
或CONST
。不用说,第二个更好。
【讨论】:
啊哈!杰出的!您是对的,将“card_id”作为 INT 进行搜索,而实际上它是 VARCHAR 使这些查询比应有的速度慢了近 600 倍!谢谢安德烈亚斯 :) 我添加了一个链接来解释这一点。读起来不错。【参考方案3】:L2G 已经总结得差不多了,虽然我怀疑这可能是因为 card_id 使用了 varchar 类型。
我实际上打印了this informative page 用于基准测试/分析快速。这是一个快速的穷人分析技术:
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 |
+--------------------------------+----------+
祝你好运,哦,请发表你的发现!
【讨论】:
这是非常有用的信息 stefgosselin,谢谢。你的预测实际上是正确的,但安德烈亚斯的解释解释了这一点。感谢您的帮助:)【参考方案4】:我很确定 MySql 对左连接有更好的优化 - 目前没有证据支持这一点。
预计到达时间:快速侦察一轮,我找不到任何具体的东西来支持我的观点,所以.....
【讨论】:
谢谢 K.Bob,我读过类似的,但喜欢你自己;找不到证据。以上是关于为啥 mySQL 查询,左连接“明显”比我的内连接快的主要内容,如果未能解决你的问题,请参考以下文章