10

我想运行以下查询:

-- Main Query    
SELECT COUNT(*) FROM table_name WHERE device_id IN 
     (SELECT DISTINCT device_id FROM table_name WHERE NAME = 'SOME_PARA')

以下查询(来自主查询的子查询):

SELECT DISTINCT device_id FROM table_name WHERE NAME = 'SOME_PARA'

在 7 秒内执行,从 2.1M 行的表中提供 2691 行。

我触发了上面的主查询,等待 5 分钟后它仍在执行。

最后,我单独执行了子查询,从结果中取出2691条记录,执行如下查询:

-- Main Query (improvised)    
SELECT COUNT(*) FROM table_name WHERE device_id IN 
     ("device_id_1", "device_id_2", ....., "device_id_2691")

令人惊讶的是,这在 40 秒内给了我答案。

是什么赋予了?为什么 MySQL 不使用我使用的相同技术并快速给出答案?难道我做错了什么?

4

4 回答 4

5

不幸的是,MySQL 不太擅长使用 IN 优化子查询。这是来自MySQL 文档

IN 的子查询优化不如 = 运算符或 IN(value_list) 运算符有效。

IN 子查询性能不佳的典型情况是子查询返回少量行但外部查询返回大量行以与子查询结果进行比较。

问题在于,对于使用 IN 子查询的语句,优化器将其重写为相关子查询。考虑以下使用不相关子查询的语句:

SELECT ... FROM t1 WHERE t1.a IN (SELECT b FROM t2);

优化器将语句重写为相关子查询:

SELECT ... FROM t1 WHERE EXISTS (SELECT 1 FROM t2 WHERE t2.b = t1.a);

如果内部和外部查询分别返回 M 和 N 行,则执行时间变为 O(M×N) 的数量级,而不是 O(M+N),因为它对于不相关的子查询。

这意味着 IN 子查询可能比使用 IN(value_list) 运算符编写的查询慢得多,该运算符列出了子查询将返回的相同值。

尝试使用 JOIN 代替。

因为 MySQL 是由内向外工作的,所以有时你可以通过将子查询包装在另一个子查询中来欺骗 MySQL,如下所示:

SELECT COUNT(*) FROM table_name WHERE device_id IN
     (SELECT * FROM (SELECT DISTINCT device_id FROM table_name WHERE NAME = 'SOME_PARA') tmp)

这是 JOIN 解决方案:

SELECT COUNT(DISTINCT t2.id) FROM table_name t1
  JOIN table_name t2
    ON t2.device_id = t1.device_id
  WHERE t1.NAME = 'SOME_PARA'

请注意,我从内部开始,也走出去。

于 2012-08-02T18:26:44.480 回答
4

编辑:我不知道在这种情况下 MySQL 愚蠢的原因是什么 :),这个错误报告似乎与案例有关。解决方法是使用 JOIN

SELECT 
    COUNT(t1.device_id) 
FROM table_name t1 
JOIN (
    SELECT DISTINCT device_id FROM table_name WHERE NAME = 'SOME_PARA'
) as t2 ON t2.device_id = t1.device_id 
于 2012-08-02T18:02:32.353 回答
2

我认为您可以将查询重写为:

 SELECT sum(NumOnDevice) 
 from (SELECT device_id, count(*) as NumOnDevice
       FROM table_name
       having sum(case when NAME = 'SOME_PARA' then 1 else 0 end) > 0
      ) t

我意识到这并不能回答您的问题,但它可能会对您有所帮助。

在优化方面,给查询提供一堆常量和给查询提供子查询(即使结果相同)之间存在天壤之别。在第一种情况下,查询优化器有更多的信息来决定查询计划。第二,信息在编译时不可用。

Mysql - 比大多数数据库更多 - 似乎根据查询的表达方式生成查询计划。SQL 被设计为一种声明性语言,而不是一种过程语言。这意味着 SQL 查询描述了所需的结果集,并且查询引擎应该决定实现该结果的最佳方法。但是,在许多情况下,必须帮助数据库引擎才能获得最佳结果。

于 2012-08-02T18:43:26.077 回答
1

看看你要求 MySQL 做什么,它必须查看 table_name 中的每条记录,确定 device_id 是否在它通过运行查询获得的列表中,然后决定是否将其添加到计数中。所以它运行了 210 万次子查询。

这也是为什么当手动定义该列表时,它可以很快地浏览它。

于 2012-08-02T17:58:45.593 回答