一、前言
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)操作步驟的;