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

Posted

技术标签:

【中文标题】应该是独立的子查询不是。为啥?【英文标题】:A subquery that should be independent is not. Why?应该是独立的子查询不是。为什么? 【发布时间】:2009-09-16 18:18:47 【问题描述】:

我有一个带有文件的表 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中出现多次,当然,因为每个文件可以有任意多次读取操作。)

【问题讨论】:

这是 MySQL 中的 bug。从 MySQL 5.6.3 开始修复。 【参考方案1】:

尝试用连接替换子查询:

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 将子查询评估为独立的。不过,我怀疑在这种情况下这是必要的。

【讨论】:

嗯。 EXPLAIN 没有识别出相关的查询,但它仍然很慢。 这在任何体面的机器上都应该很快。考虑在 reades.file_id 上添加索引,或将其设为外键。 我添加了一个外键(ALTER TABLE 读取 ADD FOREIGN KEY (file_id) REFERENCES files (file_id);),现在速度更快了。 (虽然我认为 mySQL 不支持外键?!) 创建外键意味着创建索引,这会大大加快速度。【参考方案2】:

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

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

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

【讨论】:

+1 有趣的链接;您可以编辑您的问题,而不是将链接添加为评论。【参考方案3】:

试试:

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

【讨论】:

好的。是的,我不知道为什么会这样。 第一个建议也被识别为依赖子查询。第二个有效,但速度很慢。 嗯,在我看来,MySQL 解析器故意误解了查询 - 或者注释“DEPENDENT SUBQUERY”的含义与“相关子查询”不同,后者通常用于依赖于主查询中的“当前行值”的子查询。 file_id 是否在两个表上都有索引? 没有。如果我将其编入索引,您的解决方案也很快。【参考方案4】:

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

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

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

【讨论】:

【参考方案5】:

查看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 |
+----+-------------+------------+--------+---------------+---------+---------+-----------+------+--------------------------+

【讨论】:

如果您必须为您的 READES.file_id 运行 DISTINCT,这是我们不会知道的信息。 “MySQL 有一个草率的查询优化器”是否回答了您的问题? :P 我不会想到索引列在这种查询中会有如此大的影响......我认为 mySQL 会将子查询识别为常量,但显然必须小心这样的假设.【参考方案6】:

IN 子查询在 MySQL 5.5 和更早版本中转换为 EXIST 子查询。给定的查询将被转换为以下查询:

从文件中选择 * WHERE NOT EXISTS (SELECT 1 FROM reads 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 执行之间做出基于成本的选择。

【讨论】:

以上是关于应该是独立的子查询不是。为啥?的主要内容,如果未能解决你的问题,请参考以下文章

为啥当我的子查询无效时,此 SQL 查询会起作用? - 甲骨文 [重复]

为啥 MySQL 会挂在这个简单的子查询上?

为啥我的子查询谓词中的 LIKE[c] 与此名称不匹配?

SQL 子查询

Oracle 是不是关联嵌套在任何级别的子查询?

Mysql的子查询相关知识,少但是精