以一个简单的查询存储过程为例,简单说一下sql的几种写法( 三 )


语义上变成了是查找CustomerId 为null的值 , 如果采用Index Seek的方式执行 , 这样的话逻辑上已经错误了 。
因此出现这种写法 , 为了安全起见 , 优化器只能选择一个索引的扫描(即便是字段上有索引的情况下)
可以认为是这种写法在语义支持不了相关索引的Seek , 而索引的Scan是处理这种写法的一种安全的方式
The optimiser can tell that and it plays safe. It creates plans that will always work.
That’s (one of the reasons) why in the first example it was an index scan, not an index seek.
参考这里 , 可以简单地理解成这种写法 , 语义上支持不了索引的Seek , 最多支持到index scan
至于(@p_CustomerId is null or CustomerId = @p_CustomerId )这种写法遇到本地变量的时候 , 
为什么抑制到到索引的使用 , 我之前也是没有弄清楚的 , 评论中10楼Uest 给出了解释 , 这里非常感谢Uest
如下

以一个简单的查询存储过程为例,简单说一下sql的几种写法

文章插图
 
如果我直接带入CustomerId=‘C88’ , 再来看执行计划 , 结果跟上面一样 , 但是执行计划是完全不一样的 , 这就是所谓的抑制到索引的使用 。
以一个简单的查询存储过程为例,简单说一下sql的几种写法

文章插图
 
2 , 非常非常致命的逻辑错误
/*这是另外一种类似的奇葩的写法,需要重点关注 , 真的就能满足“不管参数是否为空都满足”andOrderNumber = ISNULL( @p_OrderNumber,OrderNumber)andCustomerId= ISNULL( @p_CustomerId,CustomerId)*/对于如下这种写法:OrderNumber = ISNULL( @p_OrderNumber,OrderNumber) , 
一部分人非常推崇 , 认为这种方式简单、清晰 , 我也是醉了 , 有可能产生非常严重的逻辑错误
如果参数为null,就转换成这种语义 where 1=1 and OrderNumber = OrderNumber
目的是查询参数为null,查询条件不生效,让这个查询条件恒成立 , 恒成立吗 , 不一定 , 某些情况下就会有严重的语义错误
博主发现这个问题也是因为某些实际系统中的bug , 折腾了好久才发现这个严重的逻辑错误
对于这种写法 , 
不管是第一点说的抑制索引的问题 , 数据量大的时候是非常严重的 , 上述写法会造成全表(索引)扫描 , 有索引也用不上 , 至于全表(索引)扫描的坏处就不说了
还是第二点说的造成的逻辑错误 , 都是非常致命的
所以这种方式是最不值得推荐的 。
第三种常见的写法:参数化SQL , 推荐
create proc pr_getOrederInfo_3(@p_OrderNumberint,@p_CustomerIdvarchar(20) ,@p_OrderDateBegindatetime,@p_OrderDateEnddatetime)asbeginset nocount on;DECLARE @ParmNVARCHAR(MAX) = N'',@sqlcommandNVARCHAR(MAX) = N''SET @sqlcommand = 'SELECT [id],[OrderNumber],[CustomerId],[OrderDate],[Remark]FROM [dbo].[SaleOrder]where 1=1 'IF(@p_OrderNumber IS NOT NULL)SET @sqlcommand = CONCAT(@sqlcommand,' AND OrderNumber= @p_OrderNumber')IF(@p_CustomerId IS NOT NULL)SET @sqlcommand = CONCAT(@sqlcommand,' AND CustomerId= @p_CustomerId')IF(@p_OrderDateBegin IS NOT NULL)SET @sqlcommand = CONCAT(@sqlcommand,' AND OrderDate>=@p_OrderDateBegin ')IF(@p_OrderDateEnd IS NOT NULL)SET @sqlcommand = CONCAT(@sqlcommand,' AND OrderDate<=@p_OrderDateEnd ')SET @Parm= '@p_OrderNumberint,@p_CustomerIdvarchar(20),@p_OrderDateBegindatetime,@p_OrderDateEnddatetime 'PRINT @sqlcommandEXEC sp_executesql @sqlcommand,@Parm,@p_OrderNumber=@p_OrderNumber,@p_CustomerId=@p_CustomerId,@p_OrderDateBegin=@p_OrderDateBegin,@p_OrderDateEnd=@p_OrderDateEndend首先我们用同样的参数来执行一下查询 , 当然没问题 , 结果跟上面是一样的
以一个简单的查询存储过程为例,简单说一下sql的几种写法

文章插图
 
所谓的参数化SQL , 就是用变量当做占位符 , 通过 EXEC sp_executesql执行的时候将参数传递进去SQL中 , 在需要填入数值或数据的地方 , 使用参数 (Parameter) 来给值 , 
这样的话 , 
第一 , 既能避免第一种写法中的SQL注入问题(包括转移符的处理) , 
因为参数是运行时传递进去SQL的 , 而不是编译时传递进去的 , 传递的参数是什么就按照什么执行 , 参数本身不参与编译


推荐阅读