其實原本這個問題沒有什麼好說的,今天優化的時候遇到一個SQL語句,由於比較有意思,因此我截取、簡化了SQL語句,演示給你們看,以下所示數據庫
declare @bamboo_Code varchar(3);
set @bamboo_Code='-01';
SELECT DISTINCT yarn_lot
FROM dbo.rsjob WITH ( nolock )
WHERE RIGHT(ges_no, 3) = @bamboo_Code
AND Isnull(yarn_lot, '') <> '';
如上所示,SQL中對列yarn_log 使用了Isnull(yarn_lot, '') <> ''這種寫法,我估計書寫該SQL語句的人應該是深信了「is null 和 is not null 將會致使索引失效」這條網上流傳的教條, 至於這個建議是從哪裏流傳開來,已經沒法考證。 那麼咱們經過實踐來驗證一下is null 或 is not null 是否會致使索引失效。 app
表rsjob是一個堆表,在列yarn_lot上建有索引yarn_lot.那麼咱們經過實驗來驗證吧性能
SELECT DISTINCT yarn_lot
FROM dbo.rsjob WITH(nolock)
WHERE yarn_lot IS NOT NULL;
SELECT DISTINCT yarn_lot
FROM dbo.rsjob WITH(nolock)
WHERE yarn_lot IS NULL
如上所示,無論是IS NULL 或IS NOT NULL都走了索引查找。優化
declare @bamboo_Code varchar(3);
set @bamboo_Code='-01';
SELECT DISTINCT yarn_lot
FROM dbo.rsjob WITH ( nolock )
WHERE RIGHT(ges_no, 3) = @bamboo_Code
AND Isnull(yarn_lot, '') <> '';
SELECT DISTINCT yarn_lot
FROM dbo.rsjob WITH ( nolock )
WHERE RIGHT(ges_no, 3) = @bamboo_Code
AND yarn_lot IS NOT NULL;
另外咱們來看看這兩個原始SQL執行計劃的開銷比值爲52:48, 也就是說使用IS NOT NULL性能更好,第一個SQL語句因爲作了轉換,致使其走索引掃描,而使用IS NOT NULL則走索引查找。 spa
「is null 和 is not null 將會致使索引失效」這種坑人教條直接被推翻了。因此還在信奉這個教條的人真應該本身動手驗證一下。 code
下面咱們能夠經過實驗驗證一下,考慮到在真實環境中,可能狀況比較複雜。咱們能夠構建下面幾個場景。其實真實環境中狀況還會複雜一些。可是基本上大體有以下一些場景 blog
狀況1:堆表 謂詞上單獨索引列索引
USE Test;
GO
DROP TABLE TEST;
GO
CREATE TABLE TEST (OBJECT_ID INT, NAME VARCHAR(12));
CREATE INDEX PK_TEST ON TEST(OBJECT_ID) INCLUDE(NAME);
DECLARE @Index INT =0;
WHILE @Index < 10000
BEGIN
INSERT INTO TEST
SELECT @Index, 'kerry'+ CAST(@Index AS VARCHAR);
SET @Index = @Index +1;
END
INSERT INTO TEST
SELECT NULL, 'only test1' UNION ALL
SELECT NULL, 'only test2'
UPDATE STATISTICS TEST WITH FULLSCAN;
SELECT * FROM TEST WHERE OBJECT_ID IS NULL;
SELECT * FROM TEST WHERE OBJECT_ID IS NOT NULL;
刪除索引,創建以下索引。以下所示 ip
DROP INDEX PK_TEST ON TEST; get
CREATE INDEX PK_TEST ON TEST(OBJECT_ID)
因而可知IS NULL 或IS NOT NULL的執行計劃即與索引有關係,還跟數據分佈有必定關係。
狀況2:堆表 謂詞上無索引
USE Test;
GO
DROP TABLE TEST;
GO
CREATE TABLE TEST (OBJECT_ID INT, NAME VARCHAR(12));
DECLARE @Index INT =0;
WHILE @Index < 10000
BEGIN
INSERT INTO TEST
SELECT @Index, 'kerry'+ CAST(@Index AS VARCHAR);
SET @Index = @Index +1;
END
INSERT INTO TEST
SELECT NULL, 'only test1' UNION ALL
SELECT NULL, 'only test2'
UPDATE STATISTICS TEST WITH FULLSCAN;
SELECT * FROM TEST WHERE OBJECT_ID IS NULL;
SELECT * FROM TEST WHERE OBJECT_ID IS NOT NULL;
如上所示,若是一個堆表沒有創建任何索引,那麼使用IS NULL 或IS NOT NULL確定要走全表掃描,不過這不在咱們的討論範圍以內。而後咱們看看將索引創建在其它字段上(主要是爲了與彙集索引表對比),它依然全表掃描。
CREATE INDEX PK_TEST ON TEST(OBJECT_ID) INCLUDE(NAME);
INSERT INTO TEST
SELECT 10000, NULL UNION ALL
SELECT 10001, NULL ;
SELECT * FROM TEST WHERE NAME IS NULL;
SELECT * FROM TEST WHERE NAME IS NOT NULL;
狀況3:堆表 聯合索引列
USE Test;
GO
DROP TABLE TEST;
GO
CREATE TABLE TEST (OBJECT_ID INT, NAME VARCHAR(12), AGE INT);
CREATE INDEX IDX_TEST_N1 ON TEST(NAME, AGE);
DECLARE @Index INT =0;
WHILE @Index < 10000
BEGIN
INSERT INTO TEST
SELECT @Index, 'kerry'+ CAST(@Index AS VARCHAR), floor(rand()*100) ;
SET @Index = @Index +1;
END
INSERT INTO TEST
SELECT NULL, 'only test1', 12 UNION ALL
SELECT NULL, 'only test2',24
UPDATE STATISTICS TEST WITH FULLSCAN;
SELECT * FROM TEST WHERE NAME IS NULL;
SELECT * FROM TEST WHERE NAME IS NOT NULL;
若是聯合索引中,謂詞位於聯合索引的第二或更後位置,那麼又是什麼狀況? 從下面咱們能夠看到,SQL走全表掃描了。
DROP INDEX IDX_TEST_N1 ON TEST;
CREATE INDEX IDX_TEST_N1 ON TEST( AGE,NAME);
UPDATE STATISTICS TEST WITH FULLSCAN;
4 彙集索引表 單獨索引列
USE Test;
GO
DROP TABLE TEST;
GO
CREATE TABLE TEST (OBJECT_ID INT, NAME VARCHAR(12));
CREATE CLUSTERED INDEX PK_TEST ON TEST(OBJECT_ID)
DECLARE @Index INT =0;
WHILE @Index < 10000
BEGIN
INSERT INTO TEST
SELECT @Index, 'kerry'+ CAST(@Index AS VARCHAR);
SET @Index = @Index +1;
END
INSERT INTO TEST
SELECT NULL, 'only test1' UNION ALL
SELECT NULL, 'only test2'
SELECT * FROM TEST WHERE OBJECT_ID IS NULL;
SELECT * FROM TEST WHERE OBJECT_ID IS NOT NULL;
若是我在列NAME上面使用IS NULL 或IS NOT NULL進行查詢,你會發現執行計劃從彙集索引查找變爲了彙集索引掃描。
INSERT INTO TEST
SELECT 10000, NULL UNION ALL
SELECT 10001, NULL ;
SELECT * FROM TEST WHERE NAME IS NULL;
SELECT * FROM TEST WHERE NAME IS NOT NULL;
4 彙集索引表 聯合索引列
USE Test;
GO
DROP TABLE TEST;
GO
CREATE TABLE TEST (OBJECT_ID INT, NAME VARCHAR(12), AGE INT);
CREATE CLUSTERED INDEX PK_TEST ON TEST(OBJECT_ID)
DECLARE @Index INT =0;
WHILE @Index < 10000
BEGIN
INSERT INTO TEST
SELECT @Index, 'kerry'+ CAST(@Index AS VARCHAR), floor(rand()*100) ;
SET @Index = @Index +1;
END
INSERT INTO TEST
SELECT 10001, 'NULL', 12 UNION ALL
SELECT 10002, 'NULL',24
CREATE INDEX IDX_TEST_N2 ON TEST(NAME,AGE);
UPDATE STATISTICS TEST WITH FULLSCAN;
若是聯合索引中,謂詞位於不位於第一列,那麼IS NULL 或IS NOT NULL有會不會走索引呢?
DROP INDEX IDX_TEST_N2 ON TEST;
CREATE INDEX IDX_TEST_N2 ON TEST(AGE,NAME);
UPDATE STATISTICS TEST WITH FULLSCAN;
如上所示,它從索引查找變成索引掃描了。
小結: 1:「is null 和 is not null 將會致使索引失效」這種教條徹底是狗屎,SQL Server的索引是包含了null 值,而Oracle的索引是不包含null值的。不一樣數據庫狀況有所不一樣,不要生搬硬套。
2:若是謂詞上面創建有索引的話,基本上都會走索引,至因而走索引查找仍是索引掃描與索引類型有必定關係,也與字段位於聯合索引中位置有關係。另外,數據分佈傾斜得很是厲害也會致使其走全表掃描而不走索引,可是這並非說IS NULL 和 IS NOT NULL致使索引失效。有一點很是重要,經過觀察SQL語句而推斷執行計劃是很不現實的,須要綜合考察SQL語句所涉及表的索引、數據分佈、統計信息,才能綜合判斷,用通俗的話來講要結合具體場景。