SQL - not in 和 join

本文深入探讨了在数据库查询中使用NOT IN和JOIN两种方式的区别,通过实例展示了在特定场景下如何选择更高效的查询策略。通过将应用ID从GUID类型转换为字符串类型,性能得到了显著提升。

  今天做了7W行的数据分页 存储过程如下:

 

ALTER PROCEDURE  [dbo].[proc_select_page_top]
@pageindex INT=1,		--当前页数
@pagesize INT=5,		--每页大小
@tablename VARCHAR(1000)='',	--表名
@fields VARCHAR(1000)='',	--查询的字段集合
@keyid VARCHAR(50)='',		--主键
@condition NVARCHAR(1000)='',		--查询条件
@orderstr VARCHAR(500),			--排序条件
@totalRecord BIGINT OUTPUT		--总记录数
AS
    begin 
	IF ISNULL(@orderstr,N'')=N'' SET @orderstr=N' ORDER BY '+@keyid+N' DESC '
	IF ISNULL(@fields,N'')=N'' SET @fields=N'*'
	IF ISNULL(@condition,N'')=N'' SET @condition=N'1=1'
	DECLARE @sql NVARCHAR(4000)
	SET @sql=N'SELECT @totalRecord=COUNT(*)'
		+N' FROM '+@tablename
		+N' WHERE '+@condition
	EXEC sp_executesql @sql,N'@totalRecord INT OUTPUT',@totalRecord OUTPUT

	SET NOCOUNT ON  


	IF(@pageindex=1)
	BEGIN
		SET @sql=N'SELECT TOP '+STR(@pagesize)+N' '+@fields+N' FROM '+@tablename+N' WHERE '+@condition+N' '+@orderstr
			EXEC(@sql)
	END
	ELSE
	BEGIN
		SET @sql=N'SELECT TOP '+STR(@pagesize)+N' '+@fields+N' FROM '+@tablename+N' WHERE  '+@keyid
		+N' NOT IN(SELECT TOP '+STR((@pageindex-1)*@pagesize)+N' '+@keyid+N' FROM '
		+@tablename+N' WHERE '+@condition+N' '+@orderstr+N') AND '+@condition+N' '+@orderstr
		print @sql
		--EXEC(@sql)
	END

END


每页20条读取,开始运行起来感觉很快,但是越到后面感觉原来越慢甚至到最后页第365页,用了11秒钟去只读取,感觉性能出现了很大的问题,最后发现在not in 这里产生了瓶颈:

   

+N' NOT IN(SELECT TOP '+STR((@pageindex-1)*@pagesize)+N' '+@keyid+N' FROM '


找了很多替代的参考,都说left join  比较省事于是改测试改写成:

     原来的sql:

     

SELECT TOP   20  *  FROM  vw_EmployeeLeaves 
					WHERE  applicationid NOT IN(
					SELECT TOP 7280 applicationid 
					FROM  vw_EmployeeLeaves  
					WHERE  1 = 1 AND EmployeeStatus = 'Effective'   
					ORDER BY applicationID DESC ) AND  1 = 1 AND EmployeeStatus = 'Effective' 
					ORDER BY applicationID DESC 


 现在的SQL:

  

   SELECT TOP 20 A.* FROM  vw_EmployeeLeaves A LEFT join (
					 select top  7280 applicationid 
					 FROM  vw_EmployeeLeaves  
					 WHERE  1 = 1 AND EmployeeStatus = 'Effective'  
					 ORDER BY applicationID DESC)  as B
					on A.applicationID=B.applicationid     
					WHERE  1 = 1 AND b.applicationID is null  AND A.EmployeeStatus = 'Effective'   
					ORDER BY A.applicationID DESC


执行结果分别为11秒,120毫秒

descr		 execDate
NotIn --start	2013-12-18 15:47:41.680
NotIn --end	2013-12-18 15:47:52.730
Join start	2013-12-18 15:47:52.730
Join End	         2013-12-18 15:47:52.850

 
到此为止比较觉得用比较面议的JOIN 代替 NOT IN ,但是在准备代替JOIN前发现了为什么not in 效率很低的问题,问题出现在:

  ORDER BY A.applicationID 

applicationID 是GUID类型的,为此换成了字符 applicaitonCODE

 

执行结果分别为57毫秒,124毫秒

descr		execDate
NotIn --start	2013-12-18 15:53:25.260
NotIn --end	2013-12-18 15:53:25.323
Join start	2013-12-18 15:53:25.323
Join End	         2013-12-18 15:53:25.447

not in 和join 取舍大家自己做参考了

<think>嗯,用户问的是SQLINEXISTS的效率比较以及优化方法。首先,我得回忆一下数据库查询优化相关的知识。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 性能优化对比分析 #### 一、IN与EXISTS的底层机制 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 -- INJOIN 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、付费专栏及课程。

余额充值