MySQL:什么时候NOT IN不等于NOT EXISTS

[[195280]]

当你想对两个表进行差分运算时,你有两种选择:使用NOT EXISTS 的子查询或者NOT IN 。后者可以说更易于编写,可以使查询方法更加明显。现代数据库系统可以优化两种执行计划从而查询到类似的结果,可以在外部和内部处理查询的相关性(我说“现代”,因为在上世纪90年代中期我已经吸取教训,当时我正在使用Oracle 7.3,它没有这个功能)。

两种结构有一个很大的不同:如果子查询返回的结果为NULL,那么 NOT IN 的条件将不执行,因为 NULL不等于它或不等于其它值。但是如果你注意到这一点,它们是等价的。事实上,这些消息告诉我们,NOT IN 查询更快,人们更喜欢用它查询。

这篇文章是关于一个数据库显著变慢的情况,而空值正是罪魁祸首。

考虑以下两个可能是用来追踪点击流数据的表。由于我们跟踪匿名和注册用户, EVENTS.USER_ID是可空的。然而,当用户不空,二级指标标就会具有较高的基数。

  1. create table USERS 
  2.   ID    integer auto_increment primary key
  3.   ... 
  4.  
  5. create table EVENTS 
  6.   ID      integer auto_increment primary key
  7.   TYPE    smallint not null
  8.   USER_ID integer 
  9.   ... 
  10.  
  11. create index EVENTS_USER_IDX on EVENTS(USER_ID); 

好的,现在让我们使用这些表:从一小部分用户开始,我们想找到那些没有特定事件的用户。 使用NOT IN子句,并确保null值不出现在内部结果中,查询如下所示:

  1. select  ID 
  2. from    USERS 
  3. where   ID in (1, 7, 2431, 87142, 32768) 
  4. and     ID not in 
  5.         ( 
  6.         select  USER_ID 
  7.         from    EVENTS 
  8.         where   TYPE = 7 
  9.         and     USER_ID is not null 
  10.         ); 

对于我的测试数据集,USERS表有100,000行,EVENTS表有10,000,000行,并且EVENTS表中大约75%的USER_ID为空。 我在我的笔记本电脑上运行这条查询,它有一个Core i7处理器,12 GB的RAM和一个SSD。

我一直运行了约2分钟,这真是…哇。

让我们用NOT EXISTS和相关的子句替换NOT IN:

  1. select  ID 
  2. from    USERS 
  3. where   ID in (1, 7, 2431, 87142, 32768) 
  4. and     not exists 
  5.         ( 
  6.         select  1 
  7.         from    EVENTS 
  8.         where   USER_ID = USERS.ID 
  9.         and     TYPE = 7 
  10.         ); 

这个版本运行在0.01秒,这比我预期的时间更短。

是时候比较一下执行计划了。 ***个计划来自NOT IN查询,第二个来自NOT EXISTS。

  1. +----+--------------------+--------+------------+----------------+-----------------+-----------------+---------+------+------+----------+--------------------------+ 
  2. | id | select_type        | table  | partitions | type           | possible_keys   | key             | key_len | ref  | rows | filtered | Extra                    | 
  3. +----+--------------------+--------+------------+----------------+-----------------+-----------------+---------+------+------+----------+--------------------------+ 
  4. |  1 | PRIMARY            | USERS  | NULL       | range          | PRIMARY         | PRIMARY         | 4       | NULL |    5 |   100.00 | Using where; Using index | 
  5. |  2 | DEPENDENT SUBQUERY | EVENTS | NULL       | index_subquery | EVENTS_USER_IDX | EVENTS_USER_IDX | 5       | func |  195 |    10.00 | Using where              | 
  6. +----+--------------------+--------+------------+----------------+-----------------+-----------------+---------+------+------+----------+--------------------------+ 

执行计划几乎相同:都是从USERS表中选择行,然后使用嵌套循环连接(“DEPENDENT SUBQUERY”)从EVENTS表中检索行。都声称使用EVENTS_USER_IDX在子查询中选择行。并且他们在每一步都估计了相似的行数。

但更仔细地查看连接类型。 NOT IN版本使用 index_subquery,而NOT EXISTS版本使用 ref。再查看ref列:NOT EXISTS版本使用了对其它列的显式引用,而NOT IN使用了一个函数。这里发生了什么?

index_subquery连接类型表示MySQL将扫描索引以查找子查询的相关行。可能是这个问题吗?我不这么认为,因为EVENTS_USER_IDX索引是“narrow”类型:它只有一列,所以引擎不应该读取大量的块来查找对应的外部查询的ID行(的确,我尝试了各种查询来测试这个索引,并且所有的运行都在几百分之一秒内)。

为了获取更多信息,我转向使用“extended”执行计划。 要查看此计划,请使用explain extended作为查询前缀,并接着使用 show warnings得到被MySQL优化器优化后的查询语句。 这是从NOT IN查询得到的(为了清晰重新格式化了):

  1. /* select#1 */  select `example`.`USERS`.`ID` AS `ID`  
  2.                 from    `example`.`USERS`  
  3.                 where   ((`example`.`USERS`.`ID` in (1,7,2431,87142,32768))  
  4.                         and (not
  5.  
  6.     (`example`.`USERS`.`ID`, 
  7.  
  8.  
  9.      ( 
  10.  
  11.       ( 
  12.  
  13.        (`example`.`USERS`.`ID`) in EVENTS on EVENTS_USER_IDX checking NULL where ((`example`.`EVENTS`.`TYPE` = 7) and (`example`.`EVENTS`.`USER_ID` is not null)) having  
  14.  
  15.         (`example`.`EVENTS`.`USER_ID`)))))))  

我找不到“on EVENTS_USER_IDX checking NULL”的解释,但我认为发生的是:优化器认为它正在执行一个IN查询,可以在结果中包含NULL; 在做出此决定时,它不考虑where子句中的空检查。 因此,它将检查(examine)USER_ID为null的750万行,以及与外部查询的值匹配的几十行。 通过“检查(examine)”,我的意思是它将读取表行,然后应用不为null条件。 此外,基于运行查询所花费的时间,我认为它为外部查询中的每个候选值执行了此操作。

所以,本文的论点是:每当你想在可为空的列上使用IN或NOT IN子查询时,请重新思考并使用EXISTS或NOT EXISTS代替。

文章来源网络,作者:运维,如若转载,请注明出处:https://shuyeidc.com/wp/272062.html<

(0)
运维的头像运维
上一篇2025-05-08 13:57
下一篇 2025-05-08 13:58

相关推荐

  • 个人主题怎么制作?

    制作个人主题是一个将个人风格、兴趣或专业领域转化为视觉化或结构化内容的过程,无论是用于个人博客、作品集、社交媒体账号还是品牌形象,核心都是围绕“个人特色”展开,以下从定位、内容规划、视觉设计、技术实现四个维度,详细拆解制作个人主题的完整流程,明确主题定位:找到个人特色的核心主题定位是所有工作的起点,需要先回答……

    2025-11-20
    0
  • 社群营销管理关键是什么?

    社群营销的核心在于通过建立有温度、有价值、有归属感的社群,实现用户留存、转化和品牌传播,其管理需贯穿“目标定位-内容运营-用户互动-数据驱动-风险控制”全流程,以下从五个维度展开详细说明:明确社群定位与目标社群管理的首要任务是精准定位,需明确社群的核心价值(如行业交流、产品使用指导、兴趣分享等)、目标用户画像……

    2025-11-20
    0
  • 香港公司网站备案需要什么材料?

    香港公司进行网站备案是一个涉及多部门协调、流程相对严谨的过程,尤其需兼顾中国内地与香港两地的监管要求,由于香港公司注册地与中国内地不同,其网站若主要服务内地用户或使用内地服务器,需根据服务器位置、网站内容性质等,选择对应的备案路径(如工信部ICP备案或公安备案),以下从备案主体资格、流程步骤、材料准备、注意事项……

    2025-11-20
    0
  • 如何企业上云推广

    企业上云已成为数字化转型的核心战略,但推广过程中需结合行业特性、企业痛点与市场需求,构建系统性、多维度的推广体系,以下从市场定位、策略设计、执行落地及效果优化四个维度,详细拆解企业上云推广的实践路径,精准定位:明确目标企业与核心价值企业上云并非“一刀切”的方案,需先锁定目标客户群体,提炼差异化价值主张,客户分层……

    2025-11-20
    0
  • PS设计搜索框的实用技巧有哪些?

    在PS中设计一个美观且功能性的搜索框需要结合创意构思、视觉设计和用户体验考量,以下从设计思路、制作步骤、细节优化及交互预览等方面详细说明,帮助打造符合需求的搜索框,设计前的规划明确使用场景:根据网站或APP的整体风格确定搜索框的调性,例如极简风适合细线条和纯色,科技感适合渐变和发光效果,电商类则可能需要突出搜索……

    2025-11-20
    0

发表回复

您的邮箱地址不会被公开。必填项已用 * 标注