我想运行以下查询:
-- 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不使用我使用的相同技术并快速给出答案?我做错了吗?
答案 0 :(得分:5)
不幸的是,MySQL并不擅长使用IN优化子查询。这来自MySQL documentation:
IN的子查询优化不如=运算符有效 或者用于IN(value_list)运算符。
子查询性能差的典型情况是子查询 返回少量行,但外部查询返回一个大行 要与子查询结果进行比较的行数。
问题在于,对于使用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'
请注意,我从内部开始也出去。
答案 1 :(得分:4)
编辑:我不知道MySQL在这种情况下的愚蠢是什么原因:),this bug report似乎与案例有关。 解决方法是使用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
答案 2 :(得分: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查询描述了所需的结果集,并且查询引擎应该决定实现该结果的最佳方法。但是,在许多情况下,必须帮助数据库引擎才能获得最佳结果。
答案 3 :(得分:1)
看看你要求MySQL做什么,它必须查看table_name中的每条记录,确定device_id是否在通过运行查询获得的列表中,然后决定是否&# 39; s将它添加到计数中。所以它运行子查询2.1M次。
这也是为什么当手动定义该列表时,它可以相当快速地通过它。