我有一个表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
中多次出现,当然,因为每个文件可以有任意多次的读取操作。)
答案 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)
SELECT * FROM files WHERE file_id NOT IN (SELECT * FROM (SELECT file_id FROM reades))
答案 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执行之间做出了基于成本的选择。