SQL Server查询优化:Where(Col = @ Col或@ Col = Null)

Joh*_*ose 3 t-sql sql-server

不确定从哪里开始 - 不确定问题是我是在欺骗查询优化器,还是在涉及空值时索引的工作方式是内在的.

我遵循的一个编码约定是编写这样的存储过程:

declare procedure SomeProc
  @ID int = null
as
  select
    st.ID,st.Col1,st.Col2
  from
    SomeTable st
  where
    (st.ID = @ID or @ID is null) --works, but very slow (relatively)
Run Code Online (Sandbox Code Playgroud)

当然,在这个简单的测试用例中不是很有用,但是当您希望存储过程对整个表或符合某些条件的行进行操作时,在其他情况下非常有用.但是,当在较大的表上使用时,这是非常慢的...比我用以下内容替换where子句大约慢3-5倍:

where
    st.ID = @ID --3-5x faster than first example
Run Code Online (Sandbox Code Playgroud)

我更加困惑的是,用-1替换null使我的速度几乎与上面的"修复"WHERE子句相同:

declare procedure SomeProc
  @ID int = -1
as
  select
    st.ID,st.Col1,st.Col2
  from
    SomeTable st
  where
    (st.ID = @ID or @ID=-1) --much better... but why?
Run Code Online (Sandbox Code Playgroud)

很明显,这是无效的,这使得事情变得古怪,但为什么呢?通过检查执行计划,我不清楚答案.这是我多年来在SQL Server的各种数据库,表格和版本上注意到的,所以我认为这不是我当前环境的怪癖. 我已经通过将默认参数值从null切换到-1来解决了这个问题.我的问题是为什么这样做.

笔记

  1. SomeTable.ID已编入索引
  2. 它可能与(或者事实上可能是)SQL Server中的参数嗅探问题 参数嗅探(或欺骗) 有关.无论它的价值如何,我在每次编辑/重新编译之后几乎都使用"exec SomeProc"进行测试. proc,即省略了可选参数.

gbn*_*gbn 6

最有可能的是问题的组合

  1. 参数嗅探
  2. OR不是一个好的运算符

但是,如果没有看到计划,这些都是有根据的猜测.

参数嗅探

...的默认值为"NULL".尝试使用不同的默认值,例如-1或无默认值.

@ID = -1,默认值为NULL,参数sniffing =普通检查,因此速度更快.

您还可以在SQL Server 2008中尝试OPTIMIZE FOR UNKNOWN

OR运算符

一些想法......

如果列不可为空,则在大多数情况下,优化程序会忽略该条件

st.ID = ISNULL(@ID, st.ID)
Run Code Online (Sandbox Code Playgroud)

此外,您可以使用IF语句

IF @ID IS NULL
   SELECT ... FROM...
ELSE
   SELECT ... FROM... WHERE st.ID
Run Code Online (Sandbox Code Playgroud)

或UNION ALL以类似的方式.

就个人而言,我在大多数情况下都会使用参数屏蔽(总是)和ISNULL(我先试试)

alter procedure SomeProc
  @ID int = NULL
AS
declare @maskID int
select @maskID = @ID
...
Run Code Online (Sandbox Code Playgroud)