SQL - 优先处理WHERE条件

时间:2016-12-21 08:33:50

标签: sql sql-server performance

我有一种情况,其中一个相当简单的选择语句需要几分钟才能完成;这句话看起来像:

SELECT *
  FROM MyView
 WHERE MyFunction(Col_1, Col_2, Col_3, Col_4) = 1
   AND Col_8 = 20
;

现在,时间被分配(大致)为选择的1/3和MyFunction的调用的2/3(通过简单地注释函数调用并与完整的选择时间进行比较来测量)。 / p>

现在,仅第二个条件(即Col_8 = 20)已经减少了记录数量。

我尝试了两次运行相同的查询,首先调用函数并将函数调用为第二个,并且两者都返回相同的值(当然)但也花了相同的时间。

我想知道如果第一个条件已经失败,如何阻止函数的调用,并想到两个选择:

  1. 将第一个条件设置为Col_8 = 20,将第二个条件设置为第一个条件(即如果第一个失败则返回FALSE,否则调用该函数),

  2. 将查询构建为选择中的选择。

  3. 最好(因为可能的原因!)会有类似于某些编程语言(Ada是第一个弹出的),你可以写下这样的东西:

    <condition 1> AND THEN <condition 2>...
    

4 个答案:

答案 0 :(得分:2)

标量函数是问题!

这些操作会强制优化器执行RBAR操作,即表扫描。

更多信息:http://www.sqlservercentral.com/articles/T-SQL/135321/

所以要修复,你需要加入标量函数!你有几个选择...

  1. 将功能逻辑内联移动
  2. 将标量函数重写为表值函数
  3. 为了说明这一点,我将使用以下代码作为标量函数的[非常]基本近似来运行这些选项:

    CREATE FUNCTION MyFunction (
       @a int
     , @b int
     , @c int
     , @d int
    )
      RETURNS bit
    AS
      BEGIN
        DECLARE @return_value bit = 0;
        IF @a + @b + @c + @c > 5
          BEGIN
            SET @return_value = 1;
          END
        ;
    
        RETURN @return_value
      END
    ;
    

    内联逻辑:

    SELECT *
    FROM   MyView
    WHERE  Col_8 = 20
    AND    Col_1 + Col_2 + Col_3 + Col_4 > 5
    ;
    

    制作TVF:

    CREATE FUNCTION MyNewFunction (
       @a int
     , @b int
     , @c int
     , @d int
    )
      RETURNS TABLE
    AS
      RETURN
    SELECT Cast(CASE WHEN @a + @b + @c + @c > 5 THEN 1 ELSE 0 END AS bit) AS return_value
    ;
    

    然后叫它

    SELECT *
    FROM   MyView
     CROSS
     APPLY dbo.MyNewFunction(Col_1, Col_2, Col_3, Col_4) AS x
    WHERE  MyView.Col_8 = 20
    AND    x.return_value = 1
    ;
    

答案 1 :(得分:2)

WHERE语句中的函数很糟糕,因为必须为每一行执行它们。此外,在这种情况下,索引是不可能的。如果your function is deterministic及其使用的列来自一个表,则可以在视图的基表中使用它来创建持久计算列。该列可以在您的WHERE语句中使用,也可以编制索引以获得更好的性能:

ALTER TABLE MyBaseTable
ADD ComputedCol AS MyFunction(Col_1, Col_2, Col_3, Col_4) PERSISTED

然后,您可以使用计算列进行选择:

SELECT *
FROM MyView
WHERE ComputedCol = 1 AND Col_8 = 20

答案 2 :(得分:1)

实际上通过使用子选择你不保证任何东西,执行的顺序是未知的。

我有一个不同的建议,不确定它是否会提高性能。使用派生表:

SELECT *
INTO TMP_FOR_SELECT
FROM MyView
WHERE Col_8 = 20;

SELECT * FROM TMP_FOR_SELECT 
WHERE  MyFunction(Col_1, Col_2, Col_3, Col_4) = 1;

除此之外,我只能在Col_8上建议一个索引,这将有助于优化器更快地找到结果。

答案 3 :(得分:1)

SELECT  *
FROM    MyView
WHERE   case 
            when Col_8 = 20 
            then case 
                    when MyFunction(Col_1, Col_2, Col_3, Col_4) = 1 
                    then 1 
                 end 
        end = 1
;

如果你有Col_8的索引

SELECT      *

FROM        MyView

WHERE       Col_8 = 20 

        and case 
                when Col_8 = 20 
                then case 
                        when MyFunction(Col_1, Col_2, Col_3, Col_4) = 1 
                        then 1 
                     end 
            end = 1
;