在我过去的经验中,我总是在需要选择数据集的简单情况下使用函数,没有很多复杂的逻辑,而且我还需要传递一个参数。
我最近被告知我应该不惜一切代价避免在 MSSQL 中使用函数,因为它们经常会导致性能问题,有时它们的使用会导致索引无法正确使用。任何人都可以谈谈这一点,并进一步详细解释这是否属实,以及背后的一些原因吗?
在我过去的经验中,我总是在需要选择数据集的简单情况下使用函数,没有很多复杂的逻辑,而且我还需要传递一个参数。
我最近被告知我应该不惜一切代价避免在 MSSQL 中使用函数,因为它们经常会导致性能问题,有时它们的使用会导致索引无法正确使用。任何人都可以谈谈这一点,并进一步详细解释这是否属实,以及背后的一些原因吗?
你被天真地劝告过。
标量函数
WHERE dbo.fn_get_year(tbl.field) = 2012
将混淆tbl.field
并使其上的任何索引都无法使用。
例如,您会发现使用WHERE tbl.field >= '20120101' AND tbl.field < '20130101'
.
在第一个示例中,必须处理每条记录,因为优化器无法看穿函数并推断出符合条件的记录范围。
在第二个示例中,您非常清楚您想要从点 a 到点 b 的连续记录块。这使优化器能够使用索引进行范围查找。
表值函数
所有这些都与SELECT * FROM dbo.my_function(@parameter) AS data
. 以这种方式使用表值函数没有任何问题。
将函数的结果连接到另一个表或函数时会变得复杂。
如果函数是多语句(带有IF
块等),则在处理连接之前返回函数的整个结果集。
如果该函数是一个内联函数(只有一个RETURNS TABLE AS SELECT blah FROM blah
),那么 SQL Server 会将其视为一个宏(除非您告诉它不要这样做)。这意味着您的函数代码被替换到您的查询中,并且为您的查询构建了一个全新的执行计划。这可能意味着由于索引优化等原因,只会处理您函数中的相关记录。
简而言之,请向建议您的人询问他们的建议特别具体。如果它仍然存在never use functions
,请忽略它们。
IMO 的反做法是在查询的 WHERE 子句中使用标量函数,而不使用任何其他可以为 SQL 提供良好选择性的过滤器。
例如
SELECT columns
FROM [table]
WHERE dbo.myFunc(col1) = 55
通常会导致表扫描,而不管col1
.
正如其他人指出的那样,有一些例外,例如,可以在索引计算列中使用确定性的模式绑定函数。
例如,考虑以下确定性函数:
CREATE FUNCTION dbo.myFunc(@id int)
returns int
WITH SCHEMABINDING
AS
BEGIN
return (@id + 1)
END
给定表(使用 MSSQL 默认 PK = 聚集索引)
CREATE TABLE MyTable
(
ID INT Identity (1,1),
SomeOtherColumn VARCHAR(50),
CONSTRAINT PK_MyTable PRIMARY KEY(ID)
)
填充了约 10 万条记录
select * from MyTable where ID < 100 -- Index Seek :)
但是,运行标量函数并没有获得聚集索引的好处
select * from MyTable where dbo.MyFunc(Id) < 100 -- Index Scan :(
使用标量函数作为计算列的基础
alter table MyTable add Computed as dbo.MyFunc(ID)
select * from MyTable where Computed < 100 -- Still Index Scan :(
-- 但是,因为 Computed 列是确定性的并且是模式绑定的,所以它可以被索引:
CREATE INDEX IX1_MyTable on MyTable(Computed)
select * from MyTable where Computed < 100 -- Index Seek :)
有趣的是,现在应用该函数会导致索引查找 (SQL 2008R2)
select * from MyTable where dbo.MyFunc(ID) < 100 -- Index Seek :)