选择查询的异常MySQL行为

时间:2014-07-15 18:46:51

标签: mysql sql

我有3张表A,B和C:

  • 表A很小(~1000行)。
  • 表B有~200,000行。
  • 表C有大约220万行。

我正在运行这样的查询:

SELECT A.Id FROM A, B, C 
WHERE A.Id = B.SomeId OR (A.Id = C.SomeId AND C.SomeValue = 'X') 
INTO OUTFILE '/tmp/result.txt';
  • A.Id是表A的主键
  • B.SomeId设置了索引
  • 修改C.SomeId已设置索引
  • C.SomeVal也设置了一个索引,但它只是一个只有两个可能值的VARCHAR(1)

我认为这只需要遍历表A中的每个Id(1000行),然后可能跨其他表查询(取决于MySQL是否短路,我不知道是否存在)

但查询似乎仍然悬而未决,或者至少需要花费很长时间。如果它只需要迭代1000行,那么比我想象的要长得多。 10分钟后输出文件仍为空。如果我能提供更多信息,请告诉我。

my@laptop$ mysql --version
mysql  Ver 14.14 Distrib 5.5.37, for debian-linux-gnu (i686) using readline 6.3

修改
我正在寻找的结果是'给我表A中的所有ID,其中Id匹配B.SomeId或 ELSE Id匹配C.SomeId AND C.SomeValue等于'X'。

3 个答案:

答案 0 :(得分:5)

OR表达式通常会使MySQL难以使用索引。尝试更改为UNION

SELECT A.id
FROM A
JOIN B ON A.id = B.SomeID
UNION
SELECT A.id
FROM A
JOIN C ON A.id = C.SomeID
WHERE C.SomeValue = 'A'

来自documentation

  

最小化WHERE子句中的OR关键字。如果没有索引有助于在OR的两侧定位值,则任何行都可能是结果集的一部分,因此必须测试所有行,这需要全表扫描。如果您有一个索引有助于优化OR查询的一侧,而另一个索引有助于优化另一侧,请使用UNION运算符运行单独的快速查询并在之后合并结果。

您的查询由最后一句描述:OR查询的每一侧都有不同的索引。

答案 1 :(得分:2)

让我们走得更小。假设你的表看起来像这样:

A.ID
1
2

B.SomeID
1
3

C.SomeID | C.SomeValue
1        |   X
2        |   X

现在,让我们看看你的查询会做什么。首先,我们查看A.ID匹配和B.SomeID是否匹配。在A.ID = 1的情况下,我们有一个匹配! Sql短路。这意味着如果or的第一部分为真,则sql不会评估or的第二部分。现在,我们仍然必须加入表C.由于没有连接条件,因此对于表C,sql将A.ID与表C中的所有列进行匹配。

现在我们需要将A.ID与B中的下一行进行比较。嗯,1<>所以,我们继续讨论or的第二部分。当C.SomeID = 1时,包括该行。当C.SomeID = 2时,不包括该行。 A.ID = 1的结果是:

A.ID    | B.SomeID   | C.SomeID   | C.SomeValue
1       | 1          | 1          |  X
1       | 1          | 2          |  X
1       | 3          | 1          |  X

这显然不是您要寻找的结果表。由于您要使用表B或C加入A而不是or,因此您应该使用union

SELECT A.Id FROM A, B
WHERE A.Id = B.SomeId

Union All

Select A.ID From A, C
 Where A.Id = C.SomeId AND C.SomeValue = 'X'

Union all将第一个查询的结果放入与第二个查询的结果相同的结果表中。现在,您的问题是您只想要一个表中的A.ID而不是另一个表中的A.ID(或其他)。有几种方法可以做到这一点。在这种情况下,我将使用having和子查询。您也可以使用not exists,但我相信having将使用更少的资源。

Select T.ID
From
(SELECT A.Id FROM A, B
WHERE A.Id = B.SomeId

Union All

Select A.ID From A, C
Where A.Id = C.SomeId AND C.SomeValue = 'X') T
Group By T.ID
Having count(1) = 1

我们只希望显示一次的ID。只有在B或C中不重复id时才会有效,所以请记住这一点。由于条件基于聚合函数count,因此该规定必须在having

答案 2 :(得分:0)

我在MySQL中并不强大,但我认为这会更好:

SELECT Id FROM
( SELECT A.Id FROM A, B
  WHERE A.Id = B.SomeId
  UNION
  SELECT A.Id FROM A, C 
  WHERE A.Id = C.SomeId AND C.SomeValue = 'X'
) X
INTO OUTFILE '/tmp/result.txt';