当WHERE子句有多个IN时,MySQL会检查太多行

时间:2013-05-24 23:34:19

标签: mysql where explain

我目前正在使用MySQL 5.6.10。

我的实际查询更复杂,但这是一种重现问题的简单方法。我知道下面的查询是无用的(从x中选择id,其中id为(从...中选择id)),但它证明了我的观点。

我创建了这个表:

CREATE  TABLE test (
  id INT NOT NULL AUTO_INCREMENT ,
  PRIMARY KEY (id));

然后运行此命令5次 - 它在表中创建了50行:

INSERT INTO test (id) VALUES(null),(null),(null),(null),(null),(null),(null),(null),(null),(null);

然后运行这个解释:

EXPLAIN SELECT id FROM test WHERE 
       id in (SELECT id FROM test WHERE id < 5);

得到了这个: 4 Rows

这对我来说非常有意义。但是如果我用另一个IN向WHERE子句添加一个OR,就像这样:

EXPLAIN SELECT id FROM test WHERE 
       id IN (SELECT id FROM test WHERE id < 5)
    OR id IN (SELECT id FROM test WHERE id > 45);

突然MySQL正在查看所有50行: 50 Rows

我知道查询可以重新编写为SELECT id FROM test WHERE id < 5 OR id > 45,或者再次写入UNION等,这不是重点。关键是MySQL正在检查太多行。

如果我在第一个查询中运行FLUSH STATUS / SHOW STATUS LIKE“Handler%”,这就是我得到的:

Handler_read_key 5
Handler_external_lock 4
Handler_read_next 4
Handler_read_first 1

但如果我对第二个查询这样做,我会得到:

Handler_read_key 99
Handler_write 9
Handler_external_lock 6
Handler_read_next 59
Handler_read_first 2

为什么会有很大的不同?我想知道它是否是优化器,如果是这样,我可以在查询中包含哪些选项来阻止这种“优化”?这对我正在开发的查询具有实际意义。 MySQL不是只检查几百行,而是检查120,000行。

1 个答案:

答案 0 :(得分:0)

一般来说,RDBMS无法优化子查询,也无法优化正确的表连接。正如Rewriting Subqueries as Joins(强调补充)中所述:

  

有时,除了使用子查询之外,还有其他方法可以测试一组值中的成员资格。此外,在某些情况下,不仅可以在没有子查询的情况下重写查询,但 使用其中一些技术而不是使用子查询可能更有效 。其中之一是IN()构造:

     

例如,此查询:

SELECT * FROM t1 WHERE id IN (SELECT id FROM t2);
     

可以改写为:

SELECT DISTINCT t1.* FROM t1, t2 WHERE t1.id=t2.id;

在你的抽象案例中(即忽略了实际上对这个查询做出的其他明显改进):

SELECT DISTINCT t1.*
FROM   test t1
  JOIN test t2 USING (id)
  JOIN test t3 USING (id)
WHERE  t2.id < 5
    OR t3.id > 45;

执行计划是:

+----+-------------+-------+--------+---------------+---------+---------+------------------+------+-------------------------------------------+
| ID | SELECT_TYPE | TABLE |  TYPE  | POSSIBLE_KEYS |   KEY   | KEY_LEN |       REF        | ROWS |                   EXTRA                   |
+----+-------------+-------+--------+---------------+---------+---------+------------------+------+-------------------------------------------+
|  1 | SIMPLE      | t1    | range  | PRIMARY       | PRIMARY |       4 | (null)           |    9 | Using where; Using index; Using temporary |
|  1 | SIMPLE      | t2    | eq_ref | PRIMARY       | PRIMARY |       4 | db_2_129b4.t1.id |    1 | Using index; Distinct                     |
|  1 | SIMPLE      | t3    | eq_ref | PRIMARY       | PRIMARY |       4 | db_2_129b4.t1.id |    1 | Using index; Distinct                     |
+----+-------------+-------+--------+---------------+---------+---------+------------------+------+-------------------------------------------+

sqlfiddle上查看。