【问题标题】:Why would an IN condition be slower than "=" in sql?为什么 IN 条件会比 sql 中的“=”慢?
【发布时间】: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


【解决方案1】:

总结:这是 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 更改为 = 时,您会得到:

1 'PRIMARY' 'question_law_version' 'ALL' '' '' '' '' 10148 '在哪里使用' 2 'SUBQUERY' 'question_law_version' 'ALL' '' '' '' '' 10148 '在哪里使用' 3 'SUBQUERY' 'question_law' 'ALL' '' '' '' '' 10040 '使用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;

【讨论】:

  • 有没有办法强制优化器将子查询仅视为子查询而不是依赖子查询?
  • @Itay Moav:MySQL 应该能够自行确定哪些子查询依赖于外部查询。我仍然有点惊讶,在这种情况下,当显然没有对原始表的引用时,它认为内部查询是一个依赖查询。我可能会搜索错误数据库以查看是否有人报告了此问题。
  • @Itay Moav:我简化了查询并在更简单的查询中复制了相同的问题。我在 MySQL 中发现了一个错误报告,描述了完全相同的问题。 MySQL 开发人员承诺修复。我已经相应地更新了我的答案。我希望这能完全回答你的问题。 PS:+1 需要我做一些研究的好问题! :)
  • 我认为您需要在 DELIMITER 上的分号前留一个空格;行。
【解决方案2】:

这很有趣,但问题也可以通过准备好的语句来解决(不确定它是否适合所有人),例如:

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;

【讨论】:

  • 如果你想走那条路,然后在 SP 中创建一个临时表,其中只包含你在 IN 中想要的值并将其连接到主表。
【解决方案3】:

这是关于内部查询 a.k.a 子查询 vs 连接,而不是关于 IN vs =,原因在那篇文章中有解释。 MySQL 5.4 版本应该引入改进的优化器,可以将一些子查询重写为更有效的形式。

你能做的最糟糕的事情就是使用所谓的相关子查询 http://dev.mysql.com/doc/refman/5.1/en/correlated-subqueries.html

【讨论】:

    【解决方案4】:

    SQL 优化器并不总是按照您的预期去做。我不确定还有比这更好的答案。这就是为什么您必须检查 EXPLAIN PLAN 输出并分析您的查询以找出时间花在哪里的原因。

    【讨论】:

    • +1 推荐 EXPLAIN 作为分析查询性能的起点。
    猜你喜欢
    • 1970-01-01
    • 2018-02-24
    • 2021-01-15
    • 2014-10-04
    • 1970-01-01
    • 2015-11-02
    • 2015-10-04
    • 1970-01-01
    相关资源
    最近更新 更多