SQL SERVER 中is null 和 is not null 将会导致索引失效吗?
2015-06-04 00:01
591 查看
其实本来这个问题没有什么好说的,今天优化的时候遇到一个SQL语句,因为比较有意思,所以我截取、简化了SQL语句,演示给大家看,如下所示
如上所示,SQL中对列yarn_log使用了Isnull(yarn_lot,'')<>''这种写法,我估计书写该SQL语句的人应该是深信了“isnull和isnotnull将会导致索引失效”这条网上流传的教条,至于这个建议是从哪里流传开来,已经无法考证。那么我们通过实践来验证一下isnull或isnotnull是否会导致索引失效。
表rsjob是一个堆表,在列yarn_lot上建有索引yarn_lot.那么我们通过实验来验证吧
如上所示,不管是ISNULL或ISNOTNULL都走了索引查找。
另外我们来看看这两个原始SQL执行计划的开销比值为52:48,也就是说使用ISNOTNULL性能更好,第一个SQL语句由于做了转换,导致其走索引扫描,而使用ISNOTNULL则走索引查找。
“isnull和isnotnull将会导致索引失效”这种坑人教条直接被推翻了。所以还在信奉这个教条的人真应该自己动手验证一下。
下面我们可以通过实验验证一下,考虑到在真实环境中,可能情况比较复杂。我们可以构建下面几个场景。其实真实环境中情况还会复杂一些。但是基本上大致有如下一些场景
情况1:堆表谓词上单独索引列
删除索引,建立如下索引。如下所示
DROPINDEXPK_TESTONTEST;
CREATEINDEXPK_TESTONTEST(OBJECT_ID)
由此可见ISNULL或ISNOTNULL的执行计划即与索引有关系,还跟数据分布有一定关系。
情况2:堆表谓词上无索引
如上所示,如果一个堆表没有建立任何索引,那么使用ISNULL或ISNOTNULL肯定要走全表扫描,不过这不在我们的讨论范围之内。然后我们看看将索引建立在其它字段上(主要是为了与聚集索引表对比),它依然全表扫描。
情况3:堆表联合索引列
如果联合索引中,谓词位于联合索引的第二或更后位置,那么又是什么情况?从下面我们可以看到,SQL走全表扫描了。
4聚集索引表单独索引列
如果我在列NAME上面使用ISNULL或ISNOTNULL进行查询,你会发现执行计划从聚集索引查找变为了聚集索引扫描。
4聚集索引表联合索引列
如果联合索引中,谓词位于不位于第一列,那么ISNULL或ISNOTNULL有会不会走索引呢?
如上所示,它从索引查找变成索引扫描了。
小结:1:“isnull和isnotnull将会导致索引失效”这种教条完全是狗屎,SQLServer的索引是包含了null值,而Oracle的索引是不包含null值的。不同数据库情况有所不同,不要生搬硬套。
2:如果谓词上面建立有索引的话,基本上都会走索引,至于是走索引查找还是索引扫描与索引类型有一定关系,也与字段位于联合索引中位置有关系。另外,数据分布倾斜得非常厉害也会导致其走全表扫描而不走索引,但是这并不是说ISNULL和ISNOTNULL导致索引失效。有一点非常重要,通过观察SQL语句而推断执行计划是很不现实的,需要综合考察SQL语句所涉及表的索引、数据分布、统计信息,才能综合判断,用通俗的话来说要结合具体场景。
declare@bamboo_Codevarchar(3);
set@bamboo_Code='-01';
SELECTDISTINCTyarn_lot
FROMdbo.rsjobWITH(nolock)
WHERERIGHT(ges_no,3)=@bamboo_Code
ANDIsnull(yarn_lot,'')<>'';
如上所示,SQL中对列yarn_log使用了Isnull(yarn_lot,'')<>''这种写法,我估计书写该SQL语句的人应该是深信了“isnull和isnotnull将会导致索引失效”这条网上流传的教条,至于这个建议是从哪里流传开来,已经无法考证。那么我们通过实践来验证一下isnull或isnotnull是否会导致索引失效。
表rsjob是一个堆表,在列yarn_lot上建有索引yarn_lot.那么我们通过实验来验证吧
SELECTDISTINCTyarn_lot
FROMdbo.rsjobWITH(nolock)
WHEREyarn_lotISNOTNULL;
SELECTDISTINCTyarn_lot
FROMdbo.rsjobWITH(nolock)
WHEREyarn_lotISNULL
如上所示,不管是ISNULL或ISNOTNULL都走了索引查找。
declare@bamboo_Codevarchar(3);
set@bamboo_Code='-01';
SELECTDISTINCTyarn_lot
FROMdbo.rsjobWITH(nolock)
WHERERIGHT(ges_no,3)=@bamboo_Code
ANDIsnull(yarn_lot,'')<>'';
SELECTDISTINCTyarn_lot
FROMdbo.rsjobWITH(nolock)
WHERERIGHT(ges_no,3)=@bamboo_Code
ANDyarn_lotISNOTNULL;
另外我们来看看这两个原始SQL执行计划的开销比值为52:48,也就是说使用ISNOTNULL性能更好,第一个SQL语句由于做了转换,导致其走索引扫描,而使用ISNOTNULL则走索引查找。
“isnull和isnotnull将会导致索引失效”这种坑人教条直接被推翻了。所以还在信奉这个教条的人真应该自己动手验证一下。
下面我们可以通过实验验证一下,考虑到在真实环境中,可能情况比较复杂。我们可以构建下面几个场景。其实真实环境中情况还会复杂一些。但是基本上大致有如下一些场景
情况1:堆表谓词上单独索引列
USETest;
GO
DROPTABLETEST;
GO
CREATETABLETEST(OBJECT_IDINT,NAMEVARCHAR(12));
CREATEINDEXPK_TESTONTEST(OBJECT_ID)INCLUDE(NAME);
DECLARE@IndexINT=0;
WHILE@Index<10000
BEGIN
INSERTINTOTEST
SELECT@Index,'kerry'+CAST(@IndexASVARCHAR);
SET@Index=@Index+1;
END
INSERTINTOTEST
SELECTNULL,'onlytest1'UNIONALL
SELECTNULL,'onlytest2'
UPDATESTATISTICSTESTWITHFULLSCAN;
SELECT*FROMTESTWHEREOBJECT_IDISNULL;
SELECT*FROMTESTWHEREOBJECT_IDISNOTNULL;
删除索引,建立如下索引。如下所示
DROPINDEXPK_TESTONTEST;
CREATEINDEXPK_TESTONTEST(OBJECT_ID)
由此可见ISNULL或ISNOTNULL的执行计划即与索引有关系,还跟数据分布有一定关系。
情况2:堆表谓词上无索引
USETest;
GO
DROPTABLETEST;
GO
CREATETABLETEST(OBJECT_IDINT,NAMEVARCHAR(12));
DECLARE@IndexINT=0;
WHILE@Index<10000
BEGIN
INSERTINTOTEST
SELECT@Index,'kerry'+CAST(@IndexASVARCHAR);
SET@Index=@Index+1;
END
INSERTINTOTEST
SELECTNULL,'onlytest1'UNIONALL
SELECTNULL,'onlytest2'
UPDATESTATISTICSTESTWITHFULLSCAN;
SELECT*FROMTESTWHEREOBJECT_IDISNULL;
SELECT*FROMTESTWHEREOBJECT_IDISNOTNULL;
如上所示,如果一个堆表没有建立任何索引,那么使用ISNULL或ISNOTNULL肯定要走全表扫描,不过这不在我们的讨论范围之内。然后我们看看将索引建立在其它字段上(主要是为了与聚集索引表对比),它依然全表扫描。
CREATEINDEXPK_TESTONTEST(OBJECT_ID)INCLUDE(NAME);
INSERTINTOTEST
SELECT10000,NULLUNIONALL
SELECT10001,NULL;
SELECT*FROMTESTWHERENAMEISNULL;
SELECT*FROMTESTWHERENAMEISNOTNULL;
情况3:堆表联合索引列
USETest;
GO
DROPTABLETEST;
GO
CREATETABLETEST(OBJECT_IDINT,NAMEVARCHAR(12),AGEINT);
CREATEINDEXIDX_TEST_N1ONTEST(NAME,AGE);
DECLARE@IndexINT=0;
WHILE@Index<10000
BEGIN
INSERTINTOTEST
SELECT@Index,'kerry'+CAST(@IndexASVARCHAR),floor(rand()*100);
SET@Index=@Index+1;
END
INSERTINTOTEST
SELECTNULL,'onlytest1',12UNIONALL
SELECTNULL,'onlytest2',24
UPDATESTATISTICSTESTWITHFULLSCAN;
SELECT*FROMTESTWHERENAMEISNULL;
SELECT*FROMTESTWHERENAMEISNOTNULL;
如果联合索引中,谓词位于联合索引的第二或更后位置,那么又是什么情况?从下面我们可以看到,SQL走全表扫描了。
DROPINDEXIDX_TEST_N1ONTEST;
CREATEINDEXIDX_TEST_N1ONTEST(AGE,NAME);
UPDATESTATISTICSTESTWITHFULLSCAN;
4聚集索引表单独索引列
USETest;
GO
DROPTABLETEST;
GO
CREATETABLETEST(OBJECT_IDINT,NAMEVARCHAR(12));
CREATECLUSTEREDINDEXPK_TESTONTEST(OBJECT_ID)
DECLARE@IndexINT=0;
WHILE@Index<10000
BEGIN
INSERTINTOTEST
SELECT@Index,'kerry'+CAST(@IndexASVARCHAR);
SET@Index=@Index+1;
END
INSERTINTOTEST
SELECTNULL,'onlytest1'UNIONALL
SELECTNULL,'onlytest2'
SELECT*FROMTESTWHEREOBJECT_IDISNULL;
SELECT*FROMTESTWHEREOBJECT_IDISNOTNULL;
如果我在列NAME上面使用ISNULL或ISNOTNULL进行查询,你会发现执行计划从聚集索引查找变为了聚集索引扫描。
INSERTINTOTEST
SELECT10000,NULLUNIONALL
SELECT10001,NULL;
SELECT*FROMTESTWHERENAMEISNULL;
SELECT*FROMTESTWHERENAMEISNOTNULL;
4聚集索引表联合索引列
USETest;
GO
DROPTABLETEST;
GO
CREATETABLETEST(OBJECT_IDINT,NAMEVARCHAR(12),AGEINT);
CREATECLUSTEREDINDEXPK_TESTONTEST(OBJECT_ID)
DECLARE@IndexINT=0;
WHILE@Index<10000
BEGIN
INSERTINTOTEST
SELECT@Index,'kerry'+CAST(@IndexASVARCHAR),floor(rand()*100);
SET@Index=@Index+1;
END
INSERTINTOTEST
SELECT10001,'NULL',12UNIONALL
SELECT10002,'NULL',24
CREATEINDEXIDX_TEST_N2ONTEST(NAME,AGE);
UPDATESTATISTICSTESTWITHFULLSCAN;
如果联合索引中,谓词位于不位于第一列,那么ISNULL或ISNOTNULL有会不会走索引呢?
DROPINDEXIDX_TEST_N2ONTEST;
CREATEINDEXIDX_TEST_N2ONTEST(AGE,NAME);
UPDATESTATISTICSTESTWITHFULLSCAN;
如上所示,它从索引查找变成索引扫描了。
小结:1:“isnull和isnotnull将会导致索引失效”这种教条完全是狗屎,SQLServer的索引是包含了null值,而Oracle的索引是不包含null值的。不同数据库情况有所不同,不要生搬硬套。
2:如果谓词上面建立有索引的话,基本上都会走索引,至于是走索引查找还是索引扫描与索引类型有一定关系,也与字段位于联合索引中位置有关系。另外,数据分布倾斜得非常厉害也会导致其走全表扫描而不走索引,但是这并不是说ISNULL和ISNOTNULL导致索引失效。有一点非常重要,通过观察SQL语句而推断执行计划是很不现实的,需要综合考察SQL语句所涉及表的索引、数据分布、统计信息,才能综合判断,用通俗的话来说要结合具体场景。
相关文章推荐
- ORACLE查询小问题
- redis(jedis)相关API ,实现与关系型数据库相似的功能
- 自己动手,部署Redis服务器
- MySQL索引和优化查询
- MySQL的btree索引和hash索引的区别
- mysql查看表的编码,修改编码
- mysql emojj表情设置
- 简单的sql入门
- Redis入门系列之事务
- Redis入门系列之队列和发布订阅模式
- SQL server 触发器
- 简单sql部分强化练习题
- 说说SQL Server的数据类型
- Java并发教程(Oracle官方资料)
- 单机版的Mysql主从数据库配置
- SQLite数据库的SQLiteOpenHelper帮助类
- 常用 SQL 随笔
- Jedis 最简单的例子分析
- MYSQL基础02 - 索引的操作
- 使用mybatis执行oracle存储过程