检查问题This SELECT query takes 180 seconds to finish(查看问题本身的评论) IN只能与一个值进行比较,但时差仍然很大 为什么会那样?
答案 0 :(得分:47)
总结:这是MySQL中的known problem,并在MySQL 5.6.x中得到修复。问题是由于使用IN的子查询被错误地识别为从属子查询而不是独立子查询时缺少优化。
在原始查询上运行EXPLAIN时,它会返回:
1 'PRIMARY' 'question_law_version' 'ALL' '' '' '' '' 10148 'Using where' 2 'DEPENDENT SUBQUERY' 'question_law_version' 'ALL' '' '' '' '' 10148 'Using where' 3 'DEPENDENT SUBQUERY' 'question_law' 'ALL' '' '' '' '' 10040 'Using where'
当您将IN
更改为=
时,您会收到以下信息:
1 'PRIMARY' 'question_law_version' 'ALL' '' '' '' '' 10148 'Using where' 2 'SUBQUERY' 'question_law_version' 'ALL' '' '' '' '' 10148 'Using where' 3 'SUBQUERY' 'question_law' 'ALL' '' '' '' '' 10040 'Using where'
每个依赖子查询在包含它的查询中每行运行一次,而子查询只运行一次。当有条件可以转换为连接时,MySQL有时可以优化从属子查询,但事实并非如此。
现在这当然留下了为什么MySQL认为IN版本需要是依赖子查询的问题。我已经制作了查询的简化版本以帮助调查此问题。我创建了两个表'foo'和'bar',其中前者只包含一个id列,后者包含id和foo id(虽然我没有创建外键约束)。然后我用1000行填充了两个表:
CREATE TABLE foo (id INT PRIMARY KEY NOT NULL);
CREATE TABLE bar (id INT PRIMARY KEY, foo_id INT NOT NULL);
-- populate tables with 1000 rows in each
SELECT id
FROM foo
WHERE id IN
(
SELECT MAX(foo_id)
FROM bar
);
此简化查询具有与以前相同的问题 - 内部选择被视为从属子查询,并且不执行优化,导致内部查询每行运行一次。查询需要几秒钟才能运行。将IN
再次更改为=
可让查询几乎立即运行。
我用来填充表格的代码如下,以防有人希望重现结果。
CREATE TABLE filler (
id INT NOT NULL PRIMARY KEY AUTO_INCREMENT
) ENGINE=Memory;
DELIMITER $$
CREATE PROCEDURE prc_filler(cnt INT)
BEGIN
DECLARE _cnt INT;
SET _cnt = 1;
WHILE _cnt <= cnt DO
INSERT
INTO filler
SELECT _cnt;
SET _cnt = _cnt + 1;
END WHILE;
END
$$
DELIMITER ;
CALL prc_filler(1000);
INSERT foo SELECT id FROM filler;
INSERT bar SELECT id, id FROM filler;
答案 1 :(得分:1)
这是关于内部查询a.k.a子查询与连接,而不是关于IN vs =,蚂蚁的原因在那篇文章中有解释。 MySQL的5.4版本用于引入改进的优化器,可以将一些子查询重写为更有效的形式。
你能做的最糟糕的事情就是使用所谓的相关子查询 http://dev.mysql.com/doc/refman/5.1/en/correlated-subqueries.html
答案 2 :(得分:0)
SQL优化器并不总是按照您的期望执行。我不确定有什么比这更好的答案。这就是为什么你必须检查EXPLAIN PLAN输出,并分析你的查询以找出花费的时间。
答案 3 :(得分:0)
这很有趣,但问题也可以通过准备好的陈述来解决(不确定它是否适合所有人),例如:
mysql> EXPLAIN SELECT * FROM words WHERE word IN (SELECT word FROM phrase_words);
+----+--------------------+--------------+...
| id | select_type | table |...
+----+--------------------+--------------+...
| 1 | PRIMARY | words |...
| 2 | DEPENDENT SUBQUERY | phrase_words |...
+----+--------------------+--------------+...
mysql> EXPLAIN SELECT * FROM words WHERE word IN ('twist','rollers');
+----+-------------+-------+...
| id | select_type | table |...
+----+-------------+-------+...
| 1 | SIMPLE | words |...
+----+-------------+-------+...
所以只需在存储过程中准备语句,然后执行它。这是一个想法:
SET @words = (SELECT GROUP_CONCAT(word SEPARATOR '\',\'') FROM phrase_words);
SET @words = CONCAT("'", @words, "'");
SET @query = CONCAT("SELECT * FROM words WHERE word IN (", @words, ");";
PREPARE q FROM @query;
EXECUTE q;