我在不同的存储过程中使用了 where 子句的相同部分,我试图消除重复并确保代码不会不同步。所以我决定将这个检查提取到一个函数中。
我知道通常这不是一个好的做法,因为这会阻止 SQL Server 有效地使用索引查找,但是在我的场景中,两端都有通配符搜索,无论如何都会导致索引扫描,所以我没想到会有如此巨大的性能区别。
原始查询:
SELECT COUNT(1)
FROM MyTable
WHERE MyField LIKE '%term1%'
OR MyField LIKE '%term2%'
OR MyField LIKE '%term3%'
功能:
CREATE FUNCTION dbo.fn_MyFunc
(@CodeResult VARCHAR(MAX))
RETURNS BIT
WITH SCHEMABINDING, RETURNS NULL ON NULL INPUT
AS
BEGIN
RETURN
CASE
WHEN @CodeResult LIKE '%term1%'
OR @CodeResult LIKE '%term2%'
OR @CodeResult LIKE '%term3%'
THEN 1
ELSE 0
END;
END;
更新查询:
SELECT COUNT(1)
FROM MyTable
WHERE dbo.fn_MyFunc(MyField) = 1
查询执行计划:
我错过了什么吗?为什么相差近 10 倍?
我的猜测是,您为表中的每一行调用该函数,并且每次调用它时都会分配一个新的 4K VARCHAR,从而绕过数据库引擎能够围绕 LIKE 语句进行的调整和优化。 相反,您将取消该优化以及该列上的任何索引。
明显的区别是一个计划是并行执行的,另一个计划是串行执行的。非内联标量 UDF 会阻止并行计划。因此,在这种情况下,它们都同样效率低下,因为它们都有完整的索引扫描,但并行计划中的运行时间较短,因为它有多个工作线程并行处理(在您的情况下,DOP 可能为 8)
如果您想使其可内联,但在模块中并且不在可用内联标量 UDF 的版本/兼容级别上,则可以使用内联 TVF。
一个示例方法是
CREATE FUNCTION dbo.fn_MyFilterFunc
(@CodeResult VARCHAR(MAX))
RETURNS TABLE
AS
RETURN
SELECT 1 AS Result
WHERE @CodeResult LIKE '%term1%'
OR @CodeResult LIKE '%term2%'
OR @CodeResult LIKE '%term3%'
然后您可以将其用作
SELECT COUNT(1)
FROM MyTable
CROSS APPLY dbo.fn_MyFilterFunc(MyField)