【发布时间】:2011-03-25 22:18:05
【问题描述】:
检查问题This SELECT query takes 180 seconds to finish(检查问题本身的cmets)。
IN 只能与一个值进行比较,但时间差异仍然很大。
为什么会这样?
【问题讨论】:
-
@nos OP 提到将
INs 更改为=将时间从 180 秒减少到 0.00008s
标签: sql mysql performance comparison
检查问题This SELECT query takes 180 seconds to finish(检查问题本身的cmets)。
IN 只能与一个值进行比较,但时间差异仍然很大。
为什么会这样?
【问题讨论】:
INs 更改为 = 将时间从 180 秒减少到 0.00008s
标签: sql mysql performance comparison
总结:这是 MySQL 中的 known problem,在 MySQL 5.6.x 中已修复。问题是由于当使用 IN 的子查询被错误地识别为依赖子查询而不是独立子查询时缺少优化。
当您对原始查询运行 EXPLAIN 时,它会返回:
1 'PRIMARY' 'question_law_version' 'ALL' '' '' '' '' 10148 '在哪里使用' 2 'DEPENDENT SUBQUERY' 'question_law_version' 'ALL' '' '' '' '' 10148 '使用where' 3 'DEPENDENT SUBQUERY' 'question_law' 'ALL' '' '' '' '' 10040 '使用where'当您将 IN 更改为 = 时,您会得到:
每个相关子查询在它所在的查询中的每一行运行一次,而子查询只运行一次。当存在可以转换为连接的条件时,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;
【讨论】:
这很有趣,但问题也可以通过准备好的语句来解决(不确定它是否适合所有人),例如:
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;
【讨论】:
这是关于内部查询 a.k.a 子查询 vs 连接,而不是关于 IN vs =,原因在那篇文章中有解释。 MySQL 5.4 版本应该引入改进的优化器,可以将一些子查询重写为更有效的形式。
你能做的最糟糕的事情就是使用所谓的相关子查询 http://dev.mysql.com/doc/refman/5.1/en/correlated-subqueries.html
【讨论】:
SQL 优化器并不总是按照您的预期去做。我不确定还有比这更好的答案。这就是为什么您必须检查 EXPLAIN PLAN 输出并分析您的查询以找出时间花在哪里的原因。
【讨论】: