搜索查询中的可选参数会降低其性能

时间:2017-01-09 08:22:43

标签: sql-server tsql sql-server-2012

我有一个查询,其中包含十几个可选参数,我称之为:

select *
from table
where (@param1 is null or field1 = @param1) and
      (@param2 is null or field2 = @param2) and
      (@param3 is null or field3 = @param3) and
      (@param4 is null or field4 = @param4) and
      (@param5 is null or field5 = @param5) 

它工作得非常好,但其性能非常糟糕。只需几分钟即可完成,而同一查询仅使用每次所需的参数在几秒钟内运行。

例如,这会在几秒钟内对同一数据运行:

select *
from table
where field2 = @param2 and
      field4 = @param4 and
      field5 = @param5 

是否有可能优化第一个查询,以便使用与传递的参数对应的索引?现在我被迫从我的应用程序动态构建SQL语句,因此它只包含所需的参数(第二个示例),代码需要更多时间,并且更容易引入您在运行时仅在运行时检测到的错误参数。

谢谢。

4 个答案:

答案 0 :(得分:1)

“是否有可能优化第一个查询,以便使用与传递的参数对应的索引?”

没有

你犯了一个常见的错误,试图将多个不同的查询类型统一为一个,这是为了避免编写大量SQL的错误目的/目标。

单个查询意味着只有一个访问数据的单一访问路径,并且该单个访问路径将用于查询的每个实例(例如,无论是否真的使用了param1)

查询应该以这样的方式编写,即他们仍然允许 DBMS确定数据的访问路径。在编译查询时而不是在执行查询时完成此确定。 (这种区别因动态SQL等设施而有些模糊,但这并不意味着它已完全消失。)这意味着查询优化器无法考虑参数值是什么,对于查询的特定调用,在确定访问路径时。

您主动编写查询的方式剥夺了DBMS确定良好访问路径的可能性。而且你会得到你应得的表现。

解决方案正如您已经观察到的那样:根据实际使用的选择标准动态生成SQL(并处理所有可能的注入问题),或者(甚至更好,性能方面)提供有限的一组预定义的查询可能性。

答案 1 :(得分:1)

如果索引正确,这将允许索引搜索而不是扫描,因为它包含每列的过滤器:

select *
from testtable
where (field1 = @param1) and
      (isnull(field2, '_Dummy') = iif(@param2 is null, isnull(field2, '_Dummy'), @param2)) and
      (isnull(field3, '_Dummy') = iif(@param3 is null, isnull(field3, '_Dummy'), @param3)) and
      (isnull(field4, '_Dummy') = iif(@param4 is null, isnull(field4, '_Dummy'), @param4)) and
      (isnull(field5, '_Dummy') = iif(@param5 is null, isnull(field5, '_Dummy'), @param5))

提供的早期代码不允许使用空列值:

select *
from table
where (field1 = @param1) and
      (field2 = iif(@param2 is null, field2, @param2)) and
      (field3 = iif(@param3 is null, field3, @param3)) and
      (field4 = iif(@param4 is null, field4, @param4)) and
      (field5 = iif(@param5 is null, field5, @param5))

答案 2 :(得分:1)

在@Damien_The_Unbeliever链接的文章之后,我将添加Query Hint OPTION(RECOMPILE),因此每次调用它时都会编译它,并且将使用与传递的参数值对应的索引进行优化。

select *
from table
where (@param1 is null or field1 = @param1) and
      (@param2 is null or field2 = @param2) and
      (@param3 is null or field3 = @param3) and
      (@param4 is null or field4 = @param4) and
      (@param5 is null or field5 = @param5) 
OPTION (RECOMPILE)

但正如@Erwin_Smout所说,文章还指出,对于更复杂的查询,动态构建句子会更好。

谢谢大家。

答案 3 :(得分:1)

我通常会这样写你的查询。除非变量为null,否则它会将每列与其变量进行比较,在这种情况下,它会将列与自身进行比较。您可以添加重新编译提示,但我很少需要它。

select *
from table
where (field1 = isnull(@param1, field1) and
      (field2 = isnull(@param2, field2) and
      (field3 = isnull(@param3, field3) and
      (field4 = isnull(@param4, field4) and
      (field5 = isnull(@param5, field5)
option (recompile)

编辑: 当列可以包含null时,您可以使用isnull / coalesce编写查询以将null转换为值,例如空白或零,具体取决于您的数据类型。

select *
from table
where (isnull(field1, '') = coalesce(@param1, field1, '') and
      (isnull(field2, '') = coalesce(@param2, field2, '') and
      (isnull(field3, '') = coalesce(@param3, field3, '') and
      (isnull(field4, '') = coalesce(@param4, field4, '') and
      (isnull(field5, '') = coalesce(@param5, field5, '')
option (recompile)