MySQL:在 WHERE 子句中带有 NOT IN 的从属子查询非常慢

Posted

技术标签:

【中文标题】MySQL:在 WHERE 子句中带有 NOT IN 的从属子查询非常慢【英文标题】:MySQL : Dependent Sub Query with NOT IN in the WHERE clause is very slow 【发布时间】:2012-09-10 17:39:09 【问题描述】:

我正在使用 open Id login 审核我的应用程序中的用户详细信息。如果用户第一次登录 OPEN ID,我们将其视为注册。我正在使用此详细信息生成审核登录报告。示例表数据。

+---------+----------+-----------+---------------+
| USER_ID | PROVIDER | OPERATION | TIMESTAMP     |
+---------+----------+-----------+---------------+
|     120 | Google   | SIGN_UP   | 1347296347000 |
|     120 | Google   | SIGN_IN   | 1347296347000 |
|     121 | Yahoo    | SIGN_IN   | 1347296347000 |
|     122 | Yahoo    | SIGN_IN   | 1347296347000 |
|     120 | Google   | SIGN_UP   | 1347296347000 |
|     120 | FaceBook | SIGN_IN   | 1347296347000 |
+---------+----------+-----------+---------------+

在此表中,我想根据提供者排除已 SIGN_UP ed "SIGN_IN" ed 用户计数。

显示创建表

CREATE TABLE `signin_details` (
  `USER_ID` int(11) DEFAULT NULL,
  `PROVIDER` char(40) DEFAULT NULL,
  `OPERATION` char(40) DEFAULT NULL,
  `TIMESTAMP` bigint(20) DEFAULT NULL
) ENGINE=InnoDB

我正在使用这个查询。

select 
  count(distinct(USER_ID)) as signin_count, 
  PROVIDER from signin_details s1 
where 
  s1.USER_ID NOT IN 
  (
    select 
      USER_ID 
    from signin_details 
    where 
      signin_details.PROVIDER=s1.PROVIDER 
      and signin_details.OPERATION='SIGN_UP' 
      and signin_details.TIMESTAMP/1000 BETWEEN UNIX_TIMESTAMP(CURRENT_DATE()-INTERVAL 1 DAY) * 1000 AND UNIX_TIMESTAMP(CURRENT_DATE()) * 1000
  )  
  AND OPERATION='SIGN_IN' group by PROVIDER;

解释输出:

+----+--------------------+----------------+------+---------------+------+---------+------+------+-----------------------------+
| id | select_type        | table          | type | possible_keys | key  | key_len | ref  | rows | Extra                       |
+----+--------------------+----------------+------+---------------+------+---------+------+------+-----------------------------+
|  1 | PRIMARY            | s1             | ALL  | NULL          | NULL | NULL    | NULL |    6 | Using where; Using filesort |
|  2 | DEPENDENT SUBQUERY | signin_details | ALL  | NULL          | NULL | NULL    | NULL |    6 | Using where                 |
+----+--------------------+----------------+------+---------------+------+---------+------+------+-----------------------------+

查询输出:

+--------------+----------+
| signin_count | PROVIDER |
+--------------+----------+
|            1 | FaceBook |
|            2 | Yahoo    |
+--------------+----------+

执行 200k 行需要 40 多分钟。

我的假设是它会检查每一行的依赖子查询输出的总数。

我对此查询的假设。

 A -> Dependant Outputs (B,C,D) .
 A check with B
 A check with C
 A check with D

如果依赖查询输出较大,则执行时间会很长。如何改进这个查询?

【问题讨论】:

您基本上在 4 天前问了同样的问题,而我的回答是使用 NOT EXISTS 而不是 NOT IN。你试过吗? ***.com/questions/12305066/… 你能显示show create table......吗? 嗨沃克,我试过了,没有时间。 IN 所用的时间完全相同,不存在。 您好 jcho360,我添加了 show create table output。 你可以给你的表添加索引吗? 【参考方案1】:

如果您使用 MySQL,您必须知道子查询的执行速度非常慢。

IN 很慢...

EXISTS 通常比 IN 快​​

JOIN 是执行此类操作的最快方式。

SELECT DISTINCT
  s1.PROVIDER,
  COUNT(DISTINCT s1.USER_ID)

FROM 
  signin_details s1
  LEFT JOIN 
  (
    SELECT DISTINCT
      USER_ID, PROVIDER
    FROM 
      signin_details 
    WHERE
      signin_details.OPERATION='SIGN_UP' 
      AND 
        signin_details.TIMESTAMP 
          BETWEEN 
            UNIX_TIMESTAMP(CURRENT_DATE()-INTERVAL 1 DAY) * 1000 
            AND UNIX_TIMESTAMP(CURRENT_DATE()) * 1000
  ) AS t USING  (USER_ID, PROVIDER)

WHERE
  t.USER_ID IS NULL
  AND OPERATION='SIGN_IN'
GROUP BY s1.PROVIDER

http://sqlfiddle.com/#!2/122ac/12

注意:如果您想知道 sqlfiddle 结果,请考虑这里是查询中的UNIX_TIMESTAMP

结果:

| PROVIDER | COUNT(DISTINCT S1.USER_ID) |
-----------------------------------------
| FaceBook |                          1 |
|    Yahoo |                          2 |

mysqlINTERSECT 的故事。你会得到你不想计算的USER_IDPROVIDER 的所有组合。然后LEFT JOIN他们到你的数据。现在,您要计算的所有行都没有来自LEFT JOIN 的值。您可以通过t.USER_ID IS NULL 获得它们。


输入:

| rn° | USER_ID | PROVIDER | OPERATION |     TIMESTAMP |
-------------------------------------------------------
| 1   |     120 |   Google |   SIGN_UP | 1347296347000 | -
| 2   |     120 |   Google |   SIGN_IN | 1347296347000 | - (see rn° 1)
| 3   |     121 |    Yahoo |   SIGN_IN | 1347296347000 | Y
| 4   |     122 |    Yahoo |   SIGN_IN | 1347296347000 | Y
| 5   |     120 |   Google |   SIGN_UP | 1347296347000 | -
| 6   |     120 | FaceBook |   SIGN_IN | 1347296347000 | F
| 7   |     119 | FaceBook |   SIGN_IN | 1347296347000 | - (see rn° 8)
| 8   |     119 | FaceBook |   SIGN_UP | 1347296347000 | -

【讨论】:

【参考方案2】:

在 HAVING 子句中使用“NOT IN”。 它会比“不在哪里”更快

【讨论】:

以上是关于MySQL:在 WHERE 子句中带有 NOT IN 的从属子查询非常慢的主要内容,如果未能解决你的问题,请参考以下文章

Laravel 5.8,在 where 子句中带有 Count(*) 的棘手子查询

Hive 中带有 Join 或 Where 子句的条件

SQL Server 中带条件的 Where 子句

为啥以及何时在 WHERE 子句中带有条件的 LEFT JOIN 不等于在 ON 中的相同 LEFT JOIN? [复制]

where 子句中带有字符的整数字段返回奇怪的输出

MySQL中带有Order By子句的Rank函数