IN和EXISTS的区别和效率

本文探讨了SQL查询中in和exists操作在处理子查询时的效率差异,特别是在大数据表和小数据表之间的区别。通过实例和存储过程演示,强调了在不同场景下使用in和exists的最佳实践,以及如何利用索引来提升查询性能。

in和exists

(有索引是前提 ,子查询表大用exists,子查询表小用in)

in是把外表和内表作hash连接,而exists是对外表作loop循环,每次loop循环再对内表进行查询。 

select * from t1 where exists ( select null from t2 where y = x )
执行的过程相当于:
for x in ( select * from t1 )
   loop
      if ( exists ( select null from t2 where y = x.x )
      then 
         OUTPUT THE RECORD
      end if
end loop
          
 
Select * from T1 where x in ( select y from T2 )
执行的过程相当于:
select * 
  from t1, ( select distinct y from t2 ) t2
 where t1.x = t2.y;

 如果查询的两个表大小相当,那么用in和exists差别不大;如果两个表中一个较小一个较大,则子查询表大的用exists,子查询表小的用in;

例如:表A(小表),表B(大表)

select * from A where cc in(select cc from B)  -->效率低,用到了A表上cc列的索引;

select * from A where exists(select cc from B where cc=A.cc)  -->效率高,用到了B表上cc列的索引。

 相反的:

select * from B where cc in(select cc from A)  -->效率高,用到了B表上cc列的索引

select * from B where exists(select cc from A where cc=B.cc)  -->效率低,用到了A表上cc列的索引。

in和exists案列测试

t_member表 10万数据;t_member_service 1万数据左右; t_member_service的member_id是普通索引

CREATE TABLE `t_member` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) DEFAULT NULL,
  `sex` int(255) DEFAULT NULL,
  `password` varchar(255) DEFAULT NULL,
  `create_time` datetime DEFAULT NULL,
  `token` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `index_name` (`name`) USING BTREE COMMENT '普通索引'
) ENGINE=InnoDB AUTO_INCREMENT=126008 DEFAULT CHARSET=utf8mb4;

CREATE TABLE `t_member_service` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `member_id` int(11) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `m_mid` (`member_id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8mb4;

存储过程造数据

--- 定义存储过程
 
DELIMITER $$
DROP PROCEDURE IF EXISTS `autoinsert`$$
CREATE PROCEDURE `autoinsert`(IN insertCount INT)
BEGIN
  DECLARE count INT DEFAULT 0;
   WHILE(count < insertCount)
    DO
        INSERT INTO `springboot1`.`t_member`( `name`, `sex`, `password`, `create_time`, `token`) 
select substring(MD5(RAND()),1,8),0,substring(MD5(RAND()),1,10),SYSDATE(),substring(MD5(RAND()),1,20) from dual;
        SET count = count+1;
    END WHILE;
    END$$
DELIMITER;


-- 调用存储过程
call autoinsert(100000);


--- 定义存储过程
 
DELIMITER $$
DROP PROCEDURE IF EXISTS `autoinsert`$$
CREATE PROCEDURE `autoinsert`(IN insertCount INT)
BEGIN
  DECLARE count INT DEFAULT 0;
   WHILE(count < insertCount)
    DO
        INSERT INTO `t_member_service`( `member_id`) 
select count+1 from dual;
        SET count = count+1;
    END WHILE;
    END$$
DELIMITER;


-- 调用存储过程
call autoinsert(10000);

注意定义完存储过程,再执行存储过程

explain select * from t_member t where  EXISTS 
(select 1 from t_member_service t0 where t0.member_id = t.id);

explain select * from t_member t where  t.id  in 
(select t0.member_id from t_member_service t0 where t0.member_id = t.id);

 语句1:(耗时0.9s)

先执行主查询、再执行子查询

语句2:(耗时0.03s)

先执行子查询、再执行主查询 等价于

select * from t_member t1 ,(select DISTINCT t2.member_id from t_member_service t2) t3 where t1.id = t3.member_id

<think>嗯,用户问的是SQL中INEXISTS效率比较以及优化方法。首先,我得回忆一下数据库查询优化相关的知识。INEXISTS都是用来做子查询的,但它们的执行方式可能不同。 根据引用3提到的,Oracle的CBO优化器在处理INEXISTS时,可能会将它们转换成类似的查询结构。比如IN子查询被转换为内联视图的JOIN,而EXISTS则可能变成半连接。这说明在较新的Oracle版本中,两者的性能差异可能不大,因为优化器会进行查询重写。不过,具体情况还要看数据分布索引情况。 然后要考虑子查询的结果集大小。如果子查询结果很大,IN可能会先执行子查询,生成临时表再进行JOIN,这样可能效率不高。而EXISTS是逐行检查,可能更高效,尤其是当外层表较小时。相反,如果子查询结果小,IN可能更快,因为可以快速生成列表进行匹配。 另外,NULL值的影响也很重要。IN在处理NULL时可能会有意外结果,比如当子查询返回NULL时,IN的条件会变成未知,导致不返回任何结果。而EXISTS只关心是否存在记录,不处理NULL,所以结果更可控。这点在引用1中也提到,当列允许NULL时,两者的结果性能可能与NOT NULL列不同,需要注意。 索引的使用也是关键。无论是IN还是EXISTS,如果关联字段没有索引,性能都会差。比如,JOINB的cust_idcust_first_name是否有索引会影响查询速度。引用2提到合理使用查询重写结合执行计划分析,所以建议用户查看执行计划,确认是否走索引。 优化方法方面,可以建议用户使用EXPLAIN PLAN分析执行计划,确保正确的索引被使用。另外,考虑将子查询转换为JOIN,有时候手动优化比依赖优化器更有效。还要注意避免在子查询中使用DISTINCT或ORDER BY,因为会增加排序开销,引用4指出这些操作会导致多次排序,影响性能。 可能用户的实际场景中遇到了性能问题,想知道该用IN还是EXISTS。需要强调测试的重要性,因为不同的数据库数据情况可能导致不同的结果。同时,提醒用户注意NULL值的问题,确保查询结果的正确性。 总结一下,回答需要分点说明两者的机制、性能对比因素、优化建议,并引用提供的资料来支持观点。还要生成相关问题,帮助用户深入理解。</think>### SQL IN vs EXISTS 性能优化对比分析 #### 一、INEXISTS的底层机制 1. **IN子查询** - 通常以`内联视图`方式处理,先执行子查询生成结果集,再与外层表通过`JOIN`关联。例如: $$SELECT * FROM A WHERE id IN (SELECT id FROM B)$$ 可能被优化器转换为: $$SELECT A.* FROM A JOIN (SELECT DISTINCT id FROM B) B_sub ON A.id = B_sub.id$$ 若子查询结果集大,可能产生临时表影响性能[^3]。 2. **EXISTS子查询** - 采用`半连接(Semi-Join)`机制,逐行检查外层表记录是否满足条件,适合外层表小、子查询关联字段有索引的场景。例如: $$SELECT * FROM A WHERE EXISTS (SELECT 1 FROM B WHERE B.id = A.id)$$ 优化器可能优先使用索引快速定位匹配项[^3]。 --- #### 二、性能对比关键因素 | 场景 | IN更优 | EXISTS更优 | |---------------------|----------------------------|--------------------------| | 外层表 vs 子查询结果 | 子查询结果集小 | 外层表结果集小 | | 索引情况 | 子查询字段有唯一索引 | 关联字段有高效索引 | | NULL值处理 | 子查询结果可能包含NULL[^1] | EXISTS忽略NULL值更安全 | --- #### 三、优化建议 1. **优先检查执行计划** 使用`EXPLAIN PLAN`分析查询路径,确保关联字段(如`cust_id`)有索引覆盖。例如: ```sql -- 添加索引示例 CREATE INDEX idx_joinb_custid ON JOINB(cust_id); CREATE INDEX idx_joina_custid ON JOINA(cust_id); ``` 2. **子查询结果集控制** - 对IN子查询添加`LIMIT`或过滤条件,减少临时表大小: $$SELECT * FROM A WHERE id IN (SELECT id FROM B WHERE condition LIMIT 1000)$$ - 对EXISTS子查询确保关联条件简洁,避免复杂计算。 3. **避免冗余操作** - 移除子查询中的`DISTINCT`或`ORDER BY`(会触发额外排序)[^4]。 - 将`SELECT *`替换为`SELECT 1`(EXISTS子查询无需返回数据)。 4. **语义等价重写** 手动将IN/EXISTS转换为`JOIN`,例如: ```sql -- IN转JOIN SELECT A.* FROM A INNER JOIN B ON A.id = B.id; -- EXISTS转JOIN SELECT A.* FROM A WHERE id IN (SELECT id FROM B); ``` --- #### 四、实际案例分析 **示例表结构**(引用自[^3]): - `JOINA(cust_id, ...)` - `JOINB(cust_id, cust_first_name, ...)` **查询优化对比**: - 原IN查询: $$SELECT COUNT(*) FROM JOINA WHERE cust_id IN (SELECT cust_id FROM JOINB WHERE cust_first_name LIKE 'C%')$$ 执行计划可能生成临时表`VW_NSO_1`并进行哈希连接。 - 原EXISTS查询: $$SELECT COUNT(*) FROM JOINA A WHERE EXISTS (SELECT 1 FROM JOINB B WHERE B.cust_id = A.cust_id AND B.cust_first_name LIKE 'C%')$$ 可能通过索引快速定位匹配项,减少全表扫描。 --- #### 五、总结 - **等价值场景**:现代CBO优化器(如Oracle 10g+)可能将IN/EXISTS转换为相似执行计划[^3]。 - **优化核心**:通过索引减少IO、控制结果集大小、避免冗余操作。 - **决策建议**:外层表大用IN,外层表小用EXISTS;始终验证执行计划。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值