SQL - 优先考虑 WHERE 条件
Posted
技术标签:
【中文标题】SQL - 优先考虑 WHERE 条件【英文标题】:SQL - Prioritizing WHERE conditions 【发布时间】:2016-12-21 08:33:50 【问题描述】:我有一种情况,一个看起来相当简单的 select 语句需要几分钟才能完成;这个语句看起来像:
SELECT *
FROM MyView
WHERE MyFunction(Col_1, Col_2, Col_3, Col_4) = 1
AND Col_8 = 20
;
现在,时间分布(大致)为 select 的 1/3 和 MyFunction
调用的 2/3(通过简单地注释函数调用并与完整的 select 时间进行比较来衡量)。
现在,仅第二个条件(即Col_8 = 20
)已经减少了记录数。
我尝试运行相同的查询两次,首先调用函数,然后调用它,两者都返回相同的值(当然)但也花费了相同的时间。
我想知道如果第一个条件已经失败,如何阻止函数的调用,并想到了两个替代方案:
将第一个条件设置为Col_8 = 20
,将第二个条件设置为第一个条件的CASE(即如果第一个条件失败返回FALSE,否则调用该函数),
将查询构建为选择中的选择。
最好的(出于可能的原因!)将是在某些编程语言中(Ada 是第一个出现的),您可以在其中编写如下内容:
<condition 1> AND THEN <condition 2>...
【问题讨论】:
如果你在col_8
上添加一个索引,执行时间应该减少到函数需要的时间
看起来您正在使用标量值函数。您是否知道 SVF 是魔鬼,并且在几乎所有情况下使用内联表值函数 (TVF) 都是一个更好的主意?
谢谢@juergend。至于名字,当然是为了问题。真正的数据库有更有意义的名称。作为 fod 索引,假设 view 是给定的并且不能更改。
CASE
方法是(大部分)保证的唯一方法。 “将查询构建为选择中的选择。”不能保证,因为 SQL Server 可以推送谓词。
向我们展示视图定义及其表和索引。
【参考方案1】:
标量函数是问题!
这些操作强制优化器执行RBAR 操作,即表扫描。
更多信息:http://www.sqlservercentral.com/articles/T-SQL/135321/
因此,要修复,您需要对标量函数进行 bin 处理!你有几个选择...
-
内联移动函数逻辑
将标量函数重写为表值函数
为了说明这一点,我将使用以下代码作为标量函数的 [非常] 基本近似来运行这些选项:
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
;
【讨论】:
这是我提到的一个select中的select,也提到这个函数消耗了2/3的时间。 这不会强制执行顺序。见connect.microsoft.com/SQLServer/feedback/details/537419/… 一个巨大的NO。子查询不保证执行顺序。 哦-我不知道子查询-谢谢你们-每天都是上学日! @FDavidov 答案已更新以提供替代方法,这应该可以缓解您正在使用的标量 UDF 的性能问题。【参考方案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
【讨论】:
感谢您的回答。我的问题不在于改变现有实现中的内容,而是在于将执行顺序设置为 WHERE 子句中的条件的优先级/设置执行顺序的能力。【参考方案3】: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
;
【讨论】:
嘿嘟嘟。是的,这是我在问题中提出的选项之一,但希望有更多ELLEGANT。谢谢。【参考方案4】:实际上,通过使用子选择,您并不能保证任何事情,执行顺序是未知的。
我有一个不同的建议,不确定它是否会提高性能。使用派生表:
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
上建议一个索引,这将有助于优化器更快地找到结果。
【讨论】:
这不能保证比派生表或 CTE 更多的东西(即根本不) +1,这是对过滤器强制排序的唯一可靠方法。但是,您可能不小心使用了 ORACLE 语法而不是 SQL Server。 SQL Server 是SELECT ... INTO TMP_FOR_SELECT FROM ...
。另外,我会使用一个真正的临时表,即#tmp_for_select
。
@MartinSmith 实际上确实保证只有带有Col_8 = 20
的记录将被发送到函数。除非我错过了什么?
@Heinzi,显然,这不是唯一的方法
@DuduMarkovitz:CASE WHEN 不会总是短路,请参阅this answer 上的评论。以上是关于SQL - 优先考虑 WHERE 条件的主要内容,如果未能解决你的问题,请参考以下文章
PreparedStatement 可以不考虑 WHERE 子句中的某些条件吗?