为何在WHERE子句中使用函数会导致性能大幅差异?
为何提取通配符搜索逻辑到函数后性能暴跌近10倍?
为消除多存储过程中的重复SQL逻辑、保证代码同步,我把共用的两端通配符WHERE条件提取成了标量函数。原以为这种场景下本来就是索引扫描,性能不会有太大差异,结果实际运行后性能差了近10倍。
原查询
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
性能差距的核心原因
- 逐行执行的开销:SQL Server的标量函数默认是逐行调用,扫描MyTable的每一行时,都要单独执行一次fn_MyFunc的逻辑。而原查询的多个LIKE OR是作为集合运算的一部分,优化器可以批量处理所有行的模式匹配,效率远高于逐行调用函数。
- 优化器无法穿透函数逻辑:标量函数对查询优化器来说是“黑盒”,它无法分析函数内部的LIKE逻辑,也就做不了任何优化(比如合并模式匹配规则)。而原查询中,优化器能直接处理LIKE条件,甚至可能对多个OR的匹配逻辑做内部合并优化。
- VARCHAR(MAX)的额外损耗:函数参数用了
VARCHAR(MAX),如果MyField本身是更小的VARCHAR类型(比如VARCHAR(200)),每次调用函数都会触发隐式类型转换,这个损耗在逐行执行时会被大幅放大。
解决方案
改用**内联表值函数(ITVF)**替代标量函数,内联表值函数的逻辑会被优化器直接展开,和原查询的执行逻辑几乎一致,既能复用代码又不损失性能:
内联表值函数定义
CREATE FUNCTION dbo.fn_MyFunc_ITVF(@CodeResult VARCHAR(MAX)) RETURNS TABLE WITH SCHEMABINDING AS RETURN SELECT 1 AS MatchResult WHERE @CodeResult LIKE '%term1%' OR @CodeResult LIKE '%term2%' OR @CodeResult LIKE '%term3%';
改写后的查询
SELECT COUNT(1) FROM MyTable CROSS APPLY dbo.fn_MyFunc_ITVF(MyField) f;
如果不需要动态参数,也可以用视图封装WHERE逻辑;若需动态参数,内联表值函数是最优选择。
内容的提问来源于stack exchange,提问作者Vladimirs
相关产品推荐
相关产品推荐

