应该独立的子查询不是。为什么?

时间:2009-09-16 18:18:48

标签: sql mysql performance

我有一个表files包含文件和一个表reades,可以对这些文件进行读访问。表reades中有一列file_id,其中引用files中的相应列。

现在我想列出所有尚未访问过的文件,并尝试了这个:

SELECT * FROM files WHERE file_id NOT IN (SELECT file_id FROM reades)

这非常慢。原因是mySQL认为子查询依赖于查询:

+----+--------------------+--------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type        | table  | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
+----+--------------------+--------+------+---------------+------+---------+------+------+----------+-------------+
|  1 | PRIMARY            | files  | ALL  | NULL          | NULL | NULL    | NULL | 1053 |   100.00 | Using where |
|  2 | DEPENDENT SUBQUERY | reades | ALL  | NULL          | NULL | NULL    | NULL | 3242 |   100.00 | Using where |
+----+--------------------+--------+------+---------------+------+---------+------+------+----------+-------------+

但为什么呢?子查询是完全独立的,或多或少只是为了返回一个id列表。

(确切地说:每个file_id可以在reades中多次出现,当然,因为每个文件可以有任意多次的读取操作。)

6 个答案:

答案 0 :(得分:4)

尝试使用连接替换子查询:

SELECT * 
FROM files f
LEFT OUTER JOIN reades r on r.file_id = f.file_id
WHERE r.file_id IS NULL

这是指向article about this problem的链接。该文章的作者编写了一个存储过程来强制MySQL将子查询评估为独立的。我怀疑在这种情况下这是必要的。

答案 1 :(得分:4)

我之前见过这个。这是mysql中的一个错误。试试这个:

SELECT * FROM files WHERE file_id NOT IN (SELECT * FROM (SELECT file_id FROM reades))

此处有错误报告:http://bugs.mysql.com/bug.php?id=25926

答案 2 :(得分:2)

尝试:

SELECT * FROM files WHERE file_id NOT IN (SELECT reades.file_id FROM reades)

那就是:如果它是依赖的,也许是因为file_id引用的含糊不清,所以让我们尝试完全限定它。

如果这不起作用,请执行:

SELECT files.*
FROM files
LEFT JOIN reades
USING (file_id)
WHERE reades.file_id IS NULL

答案 3 :(得分:0)

MySQL是否像MSSQL一样支持EXISTS? 如果是这样,您可以将查询重写为

SELECT * FROM files as f WHERE file_id NOT EXISTS(SELECT 1 FROM reades r WHERE r.file_id = f.file_id)

使用IN非常低效,因为它为父查询中的每一行运行子查询。

答案 4 :(得分:0)

this page我找到了两种可行的解决方案。为了完整起见,我添加其中一个,类似于上面显示的JOIN的答案,但即使不使用外键也很快:

  SELECT * FROM files AS f 
    INNER JOIN (SELECT DISTINCT file_id FROM reades) AS r 
    ON f.file_id = r.file_id

这解决了这个问题,但这仍然没有回答我的问题:)

编辑:如果我正确解释EXPLAIN输出,这很快,因为解释器生成一个临时索引:

+----+-------------+------------+--------+---------------+---------+---------+-----------+------+--------------------------+
| id | select_type | table      | type   | possible_keys | key     | key_len | ref       | rows | Extra                    |
+----+-------------+------------+--------+---------------+---------+---------+-----------+------+--------------------------+
|  1 | PRIMARY     | <derived2> | ALL    | NULL          | NULL    | NULL    | NULL      |  843 |                          |
|  1 | PRIMARY     | f          | eq_ref | PRIMARY       | PRIMARY | 4       | r.file_id |    1 |                          |
|  2 | DERIVED     | reades     | range  | NULL          | file_id | 5       | NULL      |  811 | Using index for group-by |
+----+-------------+------------+--------+---------------+---------+---------+-----------+------+--------------------------+

答案 5 :(得分:0)

IN子查询在MySQL 5.5中,之前转换为EXIST子查询。给定的查询将转换为以下查询:

SELECT * FROM files 哪里没有EXISTS(SELECT 1 FROM reades WHERE reades.filed_id = files.file_id)

如您所见,子查询实际上是依赖的。

MySQL 5.6可以选择实现子查询。也就是说,首先,运行内部查询并将结果存储在临时表中(删除重复项)。然后,它可以使用外部表(即文件)和临时表之间的类似连接的操作来查找没有匹配的行。如果没有对reades.file_id建立索引,这种执行查询的方式可能会更加优化。

但是,如果将reades.file_id编入索引,传统的IN-to-EXISTS执行策略实际上非常有效。在这种情况下,我不希望通过将查询转换为其他答案中建议的连接而显着提高性能。 MySQL 5.6优化器在物化和IN-to-EXISTS执行之间做出了基于成本的选择。