SQL SERVER 中is null 和 is not null 将会导致索引失效吗?

本文涉及的产品
RDS SQL Server Serverless,2-4RCU 50GB 3个月
推荐场景:
云数据库 RDS SQL Server,基础系列 2核4GB
简介:

   其实本来这个问题没有什么好说的,今天优化的时候遇到一个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 是否会导致索引失效。

    表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 

clipboard

如上所示,不管是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则走索引查找。

clipboard[26]

“is null 和 is not null 将会导致索引失效”这种坑人教条直接被推翻了。所以还在信奉这个教条的人真应该自己动手验证一下。

    下面我们可以通过实验验证一下,考虑到在真实环境中,可能情况比较复杂。我们可以构建下面几个场景。其实真实环境中情况还会复杂一些。但是基本上大致有如下一些场景

 

情况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;

clipboard[1]

 

删除索引,建立如下索引。如下所示

DROP INDEX PK_TEST ON TEST;

CREATE INDEX PK_TEST ON TEST(OBJECT_ID)

clipboard[2]

    由此可见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;

clipboard[3]

如上所示,如果一个堆表没有建立任何索引,那么使用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;

clipboard[4]

 

情况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;

clipboard[5]

如果联合索引中,谓词位于联合索引的第二或更后位置,那么又是什么情况? 从下面我们可以看到,SQL走全表扫描了。

DROP INDEX IDX_TEST_N1 ON TEST;
 
CREATE INDEX IDX_TEST_N1 ON TEST( AGE,NAME);
 
UPDATE STATISTICS TEST WITH FULLSCAN;

clipboard[6]

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;

clipboard[7]

如果我在列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;

clipboard[8]

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;

clipboard[9]

如果联合索引中,谓词位于不位于第一列,那么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; 

clipboard[10]

如上所示,它从索引查找变成索引扫描了。

 

小结: 1:“is null 和 is not null 将会导致索引失效”这种教条完全是狗屎,SQL Server的索引是包含了null 值,而Oracle的索引是不包含null值的。不同数据库情况有所不同,不要生搬硬套。

       2:如果谓词上面建立有索引的话,基本上都会走索引,至于是走索引查找还是索引扫描与索引类型有一定关系,也与字段位于联合索引中位置有关系。另外,数据 分布倾斜得非常厉害也会导致其走全表扫描而不走索引,但是这并不是说IS NULL 和 IS NOT NULL导致索引失效。有一点非常重要,通过观察SQL语句而推断执行计划是很不现实的,需要综合考察SQL语句所涉及表的索引、数据分布、统计信息,才 能综合判断,用通俗的话来说要结合具体场景。

相关实践学习
使用SQL语句管理索引
本次实验主要介绍如何在RDS-SQLServer数据库中,使用SQL语句管理索引。
SQL Server on Linux入门教程
SQL Server数据库一直只提供Windows下的版本。2016年微软宣布推出可运行在Linux系统下的SQL Server数据库,该版本目前还是早期预览版本。本课程主要介绍SQLServer On Linux的基本知识。 相关的阿里云产品:云数据库RDS&nbsp;SQL Server版 RDS SQL Server不仅拥有高可用架构和任意时间点的数据恢复功能,强力支撑各种企业应用,同时也包含了微软的License费用,减少额外支出。 了解产品详情:&nbsp;https://www.aliyun.com/product/rds/sqlserver
相关文章
|
2月前
|
SQL 存储 关系型数据库
如何巧用索引优化SQL语句性能?
本文从索引角度探讨了如何优化MySQL中的SQL语句性能。首先介绍了如何通过查看执行时间和执行计划定位慢SQL,并详细解析了EXPLAIN命令的各个字段含义。接着讲解了索引优化的关键点,包括聚簇索引、索引覆盖、联合索引及最左前缀原则等。最后,通过具体示例展示了索引如何提升查询速度,并提供了三层B+树的存储容量计算方法。通过这些技巧,可以帮助开发者有效提升数据库查询效率。
167 2
|
28天前
|
SQL 存储 数据库
SQL NOT NULL
【11月更文挑战第14天】
37 6
|
2月前
|
SQL Oracle 关系型数据库
SQL优化-使用联合索引和函数索引
在一次例行巡检中,发现一条使用 `to_char` 函数将日期转换为字符串的 SQL 语句 CPU 利用率很高。为了优化该语句,首先分析了 where 条件中各列的选择性,并创建了不同类型的索引,包括普通索引、函数索引和虚拟列索引。通过对比不同索引的执行计划,最终确定了使用复合索引(包含函数表达式)能够显著降低查询成本,提高执行效率。
|
2月前
|
SQL 关系型数据库 MySQL
如何确认SQL用了索引:详细技巧与方法
在数据库管理中,索引是提高SQL查询性能的重要手段
|
2月前
|
SQL 存储 关系型数据库
SQL默认索引是什么:深入解析与技巧
在SQL数据库中,索引是一种用于提高查询性能的重要数据结构
|
2月前
|
SQL 存储 关系型数据库
SQL默认索引是什么
在SQL数据库中,索引是一种用于提高查询性能的数据结构
|
2月前
|
SQL 关系型数据库 MySQL
如何确认SQL用了索引
在数据库管理和优化过程中,确认SQL查询是否使用了索引是一个至关重要的步骤
|
2月前
|
SQL 关系型数据库 MySQL
如何确认SQL查询是否使用了索引:详细步骤与技巧
在数据库管理和优化中,确认SQL查询是否有效利用了索引是提升性能的关键步骤
|
2月前
|
索引
SQL_创建和管理索引
SQL_创建和管理索引
28 0
|
3月前
|
关系型数据库 MySQL 网络安全
5-10Can't connect to MySQL server on 'sh-cynosl-grp-fcs50xoa.sql.tencentcdb.com' (110)")
5-10Can't connect to MySQL server on 'sh-cynosl-grp-fcs50xoa.sql.tencentcdb.com' (110)")