01-in和not in的使用


一、前言

1、最近好多人都在問,in和not in到底走索引嗎?

2、not in的性能怎么樣?

基於上面的2個問題,我們具體的測試一下,實踐勝於雄辯。。。

二、in和not in是否走索引

1、准備數據

--1.創建person表
CREATE TABLE person(
    [id] [int] NULL,
    [name] [nvarchar](50) NULL,
    [age] [int] NULL
)

--插入數據
DECLARE @i int = 0;  
BEGIN TRAN  
SET @i = 10;  
WHILE (@i < 10000)  
BEGIN  
INSERT INTO customer.dbo.person VALUES (@i, substring(convert(varchar(1000),newid()),2,5),@i /5);  
SET @i += 1;  
END;  
COMMIT TRAN; 

--創建索引
CREATE NONCLUSTERED INDEX id_index ON person(id)

2、測試in是否走索引

注: 該執行計划發生了二次查找(RID Lookup),因為索引里只有id的信息,所以在索引頁中查到這條記錄后,需要通過RID(有主鍵通過主鍵)去數據頁里查詢其他的字段;

3、測試not in是否走索引

 這里使用not in后,發生了全表掃描,沒有走索引,那么就能確認not in不走索引嗎?接着往下看:

 注:這里select后面只查詢id字段,因為要是寫*的話,not in后面要寫好多值,才能走索引,因為查詢結果的數據多的話,SQLServer數據庫引擎會認為還不如走全表掃描呢。不過這也無所謂,我們只是要測試一下not in是否走索引,所以看到not in發生了全表掃描,不能就認為not in 這個語法不走索引了;

三、not in的性能測試

1、再創建一張addressdetail表

--創建addressdetail表
CREATE TABLE addressdetail(
    [id] [int] NULL,
    [tname] [nvarchar](20) NULL,
    [depart] [nvarchar](50) NULL,
    [city] [nvarchar](20) NULL
)

--插入數據
DECLARE @i int = 0;  
BEGIN TRAN  
SET @i = 14000;  
WHILE (@i < 17000)  
BEGIN  
INSERT INTO customer.dbo.addressdetail VALUES (@i, 
substring(convert(varchar(1000),newid()),2,5),
substring(convert(varchar(1000),newid()),2,8),
substring(convert(varchar(1000),newid()),2,3)
);  
SET @i += 1;  
END;  
COMMIT TRAN; 

--創建索引
create nonclustered index  ind_id  on addressdetail(id)

注:2張表(person和addressdetail)的id都允許為null,並且都是索引列

2、查看not in的執行計划

select * from person where id not in (select id from addressdetail )

 注:在圖中我們可以看到有一個Row Count Spool(Lazy Spool)操作,該操作就是確認addressdetail表中的id列是否有null值(因為該列的屬性是允許為null的,所以SQLServer必須額外確認),並且該操作占用的開銷也比較多,接近一半的查詢成本,因此在這一步是比較浪費性能的;

3、使用 not exists 代替not in ,對比SQL執行計划的查詢開銷

--查詢not in的執行計划
select * from person where id not in (select id from addressdetail )

--使用 not exists 代替not in 查看SQL的執行計划
select * from person p where  not exists (select * from addressdetail b where p.id=b.id)

注:由上圖可以看出,使用not in的SQL的查詢開銷是使用not exists的SQL的10倍,僅僅是not in需要確認id列中是否有null值;
當然這個10倍這個值是不准確的,因為這個和2張表的數據量有關,但是可以肯定的是not in的查詢性能確實是多了一步校驗null值的步驟,所以會降低性能。

4、對比not in 和not exists的IO情況 

--not in
set  statistics io on
select * from person where id not in (select id from addressdetail ) 

--not exists
select * from person p where  not exists (select * from addressdetail b where p.id=b.id)

 注:由上面可以看出not吃掉的IO很高

5、not in的結果准確性

由上面的測試,很容易看出使用not in語法是會降低SQL性能的,但是拋去性能的原因,使用not in 還有可能使查詢的結果不准確;

(1)結果不准確主要是和NULL值進行對比的時候,可能會導致結果不准確,我們知道null值並不是一個值,任何與NULL值進行比對的二元操作結果都是null,包括null值本身;

(2)比對的結果為null時,轉換為Bool類型的結果就是False;

(3)結果不准確的示例

 

 

 (4)為什么結果不准確

--not in 語句
select * from Test.dbo.teachclass where classnum  not in('303',null)

--not in('303',null) 等價於下面的語句
select * from Test.dbo.teachclass where classnum<> '303' and classnum<> null

 

 

 注:把not in的where條件語句等價用and連接,結果也為null,因為上面說了任何值與null值進行二元操作都為null,並且轉換為bool都是false,所以再做and,結果都是false,所以最后沒有結果;

解決辦法:使用not Exists作為替代。Exists操作符不會返回null,只會根據子查詢中的每一行返回true或者false,當遇到null時,只會返回false,不會因為某個null值導致整個查詢返回空。

四、總結

1、not in和in走不走索引,是視情況而看的,不能絕對的說in和not不走索引;

2、對於數據量大的表,使用IN和NOT IN往往效率很低,如果字段值允許為null,還有可能出現結果不准確的情況,所以在盡量避免使用not in;

3、如果列的屬性是not null 的話,是不會產生Row Count Spool(Lazy Spool)操作步驟的;

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM