MySQL - SELECT WHERE field IN(子查询) - 为啥非常慢?

Posted

技术标签:

【中文标题】MySQL - SELECT WHERE field IN(子查询) - 为啥非常慢?【英文标题】:MySQL - SELECT WHERE field IN (subquery) - Extremely slow why?MySQL - SELECT WHERE field IN(子查询) - 为什么非常慢? 【发布时间】:2011-09-02 09:13:45 【问题描述】:

我想检查一个数据库中有几个重复项,所以我做了什么来查看哪些是重复项,我这样做了:

SELECT relevant_field
FROM some_table
GROUP BY relevant_field
HAVING COUNT(*) > 1

这样,我将获得所有相关字段出现不止一次的行。此查询需要几毫秒才能执行。

现在,我想检查每个重复项,所以我想我可以在上面查询中使用相关字段选择 some_table 中的每一行,所以我这样做了:

SELECT *
FROM some_table 
WHERE relevant_field IN
(
    SELECT relevant_field
    FROM some_table
    GROUP BY relevant_field
    HAVING COUNT(*) > 1
)

由于某种原因,这变得非常缓慢(需要几分钟)。这里到底发生了什么让它这么慢?相关字段已编入索引。

最终我尝试从第一个查询 (SELECT relevant_field FROM some_table GROUP BY relevant_field HAVING COUNT(*) > 1) 创建一个视图“temp_view”,然后像这样进行第二个查询:

SELECT *
FROM some_table
WHERE relevant_field IN
(
    SELECT relevant_field
    FROM temp_view
)

而且效果很好。 mysql 在几毫秒内完成此操作。

这里有任何 SQL 专家可以解释发生了什么吗?

【问题讨论】:

你到底想要什么?想要删除除一个之外的重复条目??建议:请阅读Self Join 显然是慢的group-by ... 第一个查询以毫秒为单位执行(使用 HAVING 进行分组和过滤)。它仅与其他查询结合使用会使一切变慢(需要几分钟)。 @diEcho,我想查找重复项,检查它们,然后手动删除一些。 【参考方案1】:

正在为每一行运行子查询,因为它是一个相关查询。通过从子查询中选择所有内容,可以将相关查询变为非相关查询,如下所示:

SELECT * FROM
(
    SELECT relevant_field
    FROM some_table
    GROUP BY relevant_field
    HAVING COUNT(*) > 1
) AS subquery

最终查询如下所示:

SELECT *
FROM some_table
WHERE relevant_field IN
(
    SELECT * FROM
    (
        SELECT relevant_field
        FROM some_table
        GROUP BY relevant_field
        HAVING COUNT(*) > 1
    ) AS subquery
)

【讨论】:

这对我来说效果非常好。我在一个 IN(子查询)中有另一个 IN(子查询),它花了 10 多分钟,所以我在等待时用谷歌搜索。按照您的建议将每个子查询包装在 SELECT * FROM () 中将其缩短到 2 秒! 谢谢,几个小时以来,我一直在尝试找出一个好方法来做到这一点。这非常有效。希望我能给你更多的赞成票!这肯定是答案。 完美运行。运行大约 50 秒的查询现在是瞬时的。希望我能投票更多。有时你不能使用连接,所以这是正确的答案。 我想知道为什么优化器会考虑与联合相关的查询......无论如何,这个技巧就像魔术一样 您能否解释一下是什么使该子查询成为相关子查询?我的理解是,当子查询使用依赖于外部查询的值时,它会变得相关。但在这个例子中,我看不到任何相互依赖关系。对于外部查询返回的每一行,它都会给出相同的结果。我在 MariaDB 上实现了一个类似的示例,但我看不到任何性能损失(到目前为止),所以我想清楚地看到,何时需要这种 SELECT * 包装。【参考方案2】:

将查询改写成这样

SELECT st1.*, st2.relevant_field FROM sometable st1
INNER JOIN sometable st2 ON (st1.relevant_field = st2.relevant_field)
GROUP BY st1.id  /* list a unique sometable field here*/
HAVING COUNT(*) > 1

我认为st2.relevant_field 必须在选择中,因为否则having 子句会出错,但我不是100% 确定

永远不要在子查询中使用IN;这是出了名的慢。 仅使用带有固定值列表的IN

更多提示

    如果您想更快地进行查询, 不要做SELECT * 只选择 您真正需要的字段。 确保您在 relevant_field 上有一个索引,以加快 equi-join。 确保在主键上group by。 如果您使用 InnoDB 并且您只选择索引字段(事情并不太复杂),那么 MySQL 将仅使用索引来解析您的查询,从而加快处理速度向上。

90% 的 IN (select 查询的通用解决方案

使用此代码

SELECT * FROM sometable a WHERE EXISTS (
  SELECT 1 FROM sometable b
  WHERE a.relevant_field = b.relevant_field
  GROUP BY b.relevant_field
  HAVING count(*) > 1) 

【讨论】:

您也可以使用HAVING COUNT(*) > 1 编写。在 MySQL 中它通常更快。 @ypercube,为底部查询完成,我认为对于顶部查询它会改变结果。 @Johan:由于st2.relevant_field 不是NULL(它已经包含在ON 子句中),它不会改变结果。 @ypercube,因此您可以将 count(afield) 更改为 count(*) 如果您确定 afield 永远不会是 null,明白了。谢谢 @quano,是的,它列出了 所有 个重复项,因为 group byst1.id 上,而不是在 st1.relevant_field 上。【参考方案3】:
SELECT st1.*
FROM some_table st1
inner join 
(
    SELECT relevant_field
    FROM some_table
    GROUP BY relevant_field
    HAVING COUNT(*) > 1
)st2 on st2.relevant_field = st1.relevant_field;

我已经在我的一个数据库上尝试过您的查询,还尝试将其重写为子查询的连接。

这个效果更快,试试吧!

【讨论】:

是的,这可能会创建一个包含组结果的临时表,因此它与视图版本的速度相同。但是查询计划应该说实话。【参考方案4】:

我已经用 www.prettysql.net 重新格式化了你的慢速 sql 查询

SELECT *
FROM some_table
WHERE
 relevant_field in
 (
  SELECT relevant_field
  FROM some_table
  GROUP BY relevant_field
  HAVING COUNT ( * ) > 1
 );

在查询和子查询中都使用表时,您应该始终为两者设置别名,如下所示:

SELECT *
FROM some_table as t1
WHERE
 t1.relevant_field in
 (
  SELECT t2.relevant_field
  FROM some_table as t2
  GROUP BY t2.relevant_field
  HAVING COUNT ( t2.relevant_field ) > 1
 );

这有帮助吗?

【讨论】:

不幸的是,它没有帮助。它的执行速度一样慢。 我已经更新了我的答案,你可以再试一次吗?即使 group by 很慢,也应该只执行一次... 我上次不小心杀死了一个live mysql server,所以我现在恐怕不能尝试这个。稍后我将不得不设置一个测试数据库。但我不明白为什么这会影响查询。 HAVING 语句应该只适用于它所在的查询,不是吗?我真的不明白为什么“真正的”查询会影响子查询。 我发现了这个:xaprb.com/blog/2006/04/30/…。我认为这可能是解决方案。有时间会试试的。【参考方案5】:

Subqueries vs joins

http://www.scribd.com/doc/2546837/New-Subquery-Optimizations-In-MySQL-6

【讨论】:

我怀疑这样的事情,子查询正在为每一行运行。 某些 MySQL 版本甚至不使用 IN 中的索引。我添加了另一个链接。 MySQL 6 还不稳定,我不建议将其用于生产! 我不会推荐它。但这里解释了它是如何在内部运行的(4.1/5.x -> 6)。这说明了当前版本的一些缺陷。【参考方案6】:

试试这个

SELECT t1.*
FROM 
 some_table t1,
  (SELECT relevant_field
  FROM some_table
  GROUP BY relevant_field
  HAVING COUNT (*) > 1) t2
WHERE
 t1.relevant_field = t2.relevant_field;

【讨论】:

【参考方案7】:

首先你可以找到重复的行,然后找到行数被使用了多少次,然后像这样按数字排序;

SELECT q.id,q.name,q.password,q.NID,(select count(*) from UserInfo k where k.NID= q.NID) as Count,
(
		CASE q.NID
		WHEN @curCode THEN
			@curRow := @curRow + 1
		ELSE
			@curRow := 1
		AND @curCode := q.NID
		END
	) AS No
FROM UserInfo q,
(
		SELECT
			@curRow := 1,
			@curCode := ''
	) rt
WHERE q.NID IN
(
    SELECT NID
    FROM UserInfo
    GROUP BY NID
    HAVING COUNT(*) > 1
) 

之后创建一个表并将结果插入其中。

create table CopyTable 
SELECT q.id,q.name,q.password,q.NID,(select count(*) from UserInfo k where k.NID= q.NID) as Count,
(
		CASE q.NID
		WHEN @curCode THEN
			@curRow := @curRow + 1
		ELSE
			@curRow := 1
		AND @curCode := q.NID
		END
	) AS No
FROM UserInfo q,
(
		SELECT
			@curRow := 1,
			@curCode := ''
	) rt
WHERE q.NID IN
(
    SELECT NID
    FROM UserInfo
    GROUP BY NID
    HAVING COUNT(*) > 1
) 

最后,删除重复的行。No 是从 0 开始。除了每个组的第一个数字之外,删除所有重复的行。

delete from  CopyTable where No!= 0;

【讨论】:

【参考方案8】:

有时当数据变大时,由于查询优化,mysql WHERE IN 可能会非常慢。尝试使用 STRAIGHT_JOIN 告诉 mysql 按原样执行查询,例如

SELECT STRAIGHT_JOIN table.field FROM table WHERE table.id IN (...)

但要注意:在大多数情况下 mysql 优化器工作得很好,所以我建议仅在遇到此类问题时使用它

【讨论】:

【参考方案9】:

这与我的情况类似,我有一个名为 tabel_buku_besar 的表。我需要的是

    寻找在tabel_buku_besar 中具有account_code='101.100' 且具有companyarea='20000' 并且还具有IDR 作为currency 的记录

    我需要从tabel_buku_besar 获取所有记录,其中 account_code 与第 1 步相同,但在第 1 步结果中有 transaction_number

在使用select ... from...where....transaction_number in (select transaction_number from ....) 时,我的查询运行非常缓慢,有时会导致请求超时或我的应用程序没有响应...

我尝试了这个组合,结果……还不错……

`select DATE_FORMAT(L.TANGGAL_INPUT,'%d-%m-%y') AS TANGGAL,
      L.TRANSACTION_NUMBER AS VOUCHER,
      L.ACCOUNT_CODE,
      C.DESCRIPTION,
      L.DEBET,
      L.KREDIT 
 from (select * from tabel_buku_besar A
                where A.COMPANYAREA='$COMPANYAREA'
                      AND A.CURRENCY='$Currency'
                      AND A.ACCOUNT_CODE!='$ACCOUNT'
                      AND (A.TANGGAL_INPUT BETWEEN STR_TO_DATE('$StartDate','%d/%m/%Y') AND STR_TO_DATE('$EndDate','%d/%m/%Y'))) L 
INNER JOIN (select * from tabel_buku_besar A
                     where A.COMPANYAREA='$COMPANYAREA'
                           AND A.CURRENCY='$Currency'
                           AND A.ACCOUNT_CODE='$ACCOUNT'
                           AND (A.TANGGAL_INPUT BETWEEN STR_TO_DATE('$StartDate','%d/%m/%Y') AND STR_TO_DATE('$EndDate','%d/%m/%Y'))) R ON R.TRANSACTION_NUMBER=L.TRANSACTION_NUMBER AND R.COMPANYAREA=L.COMPANYAREA 
LEFT OUTER JOIN master_account C ON C.ACCOUNT_CODE=L.ACCOUNT_CODE AND C.COMPANYAREA=L.COMPANYAREA 
ORDER BY L.TANGGAL_INPUT,L.TRANSACTION_NUMBER`

【讨论】:

【参考方案10】:

我发现这对于查找值是否存在是最有效的,可以很容易地反转逻辑以查找值是否不存在(即 IS NULL);

SELECT * FROM primary_table st1
LEFT JOIN comparision_table st2 ON (st1.relevant_field = st2.relevant_field)
WHERE st2.primaryKey IS NOT NULL

*将相关字段替换为您要检查的值的名称是否存在于您的表中

*将primaryKey替换为对比表中主键列的名称。

【讨论】:

【参考方案11】:

这很慢,因为每次比较relevant_fieldIN 子句的子查询时,都会执行一次子查询。你可以这样避免:

SELECT *
FROM some_table T1 INNER JOIN 
(
    SELECT relevant_field
    FROM some_table
    GROUP BY relevant_field
    HAVING COUNT(*) > 1
) T2 
USING(relevant_field)

这将创建一个派生表(在内存中,除非它太大而无法容纳)作为 T2,然后 INNER JOIN 将它与 T1 一起创建。 JOIN 发生一次,因此查询执行一次。

我发现这对于优化使用数据透视表将批量数据表与更具体的数据表相关联并且您希望根据更具体的相关行的子集生成批量表的计数的情况特别方便。如果您可以将批量行缩小到

即您有一个用户表(条件)、一个订单表(数据透视表)和一个订单项表(批量),它们引用了产品的计数。您想要在 PostCode '90210' 中按用户分组的产品总和。在这种情况下,JOIN 将比使用WHERE relevant_field IN( SELECT * FROM (...) T2 ) 时小几个数量级,因此速度更快,尤其是当 JOIN 溢出到磁盘时!

【讨论】:

以上是关于MySQL - SELECT WHERE field IN(子查询) - 为啥非常慢?的主要内容,如果未能解决你的问题,请参考以下文章

MySQL Select 语句,WHERE 'IN' 子句

具有多个 WHERE 和 SELECT 条件的更新 - MySql

在mysql select语句中使用条件where子句

[MySQL] 过滤数据

mysql select 字段别名是否可以用在 select中或者where中

当列不明确时,有啥方法可以强制 Mysql 在 where 子句中使用 select 中的列?