SQL 中函数调用的代价分析

14次阅读

会,标量函数(如UPPER、CONVERT)在WHERE中逐行计算且阻断索引;ITVF可展开优化,MSTVF和UDF易致统计丢失、序列化执行;应避免列上函数,改用参数端转换或常量替换。

SQL 中函数调用的代价分析

sql 函数调用会拖慢查询吗? 会,但取决于函数类型和使用方式。标量函数(如 UPPER()DATEADD())在 WHERE 或 select 中对每行逐个计算,可能阻断索引下推、抑制并行执行,甚至让优化器放弃估算。而内联表值函数(ITVF)通常可展开为等价 SQL,代价接近原生查询;多语句表值函数(MSTVF)则像黑盒,常导致统计信息丢失和嵌套循环连接。

WHERE 子句里用 CONVERT()CAST() 为什么查得慢? 这类显式转换常出现在“把字段转成字符串再模糊匹配”场景,例如:WHERE CAST(order_id AS VARCHAR) LIKE '123%'。问题在于:

  • 字段被函数包裹后,无法走 order_id 列上的索引(即使该列是整型主键)
  • SQL Server 无法对转换结果生成有效统计直方图,优化器容易低估/高估行数
  • 如果 order_id 是聚集索引键,强制转换还会阻止范围扫描,退化为全索引扫描

更稳妥的做法是反向转换:把参数转为目标列类型,例如 WHERE order_id >= 12300 AND order_id (若业务允许前缀数字解析)。

GETDATE()SYSDATETIME() 在 WHERE 中能用索引吗? 可以,但仅限于“列 >= 函数调用”这类单调递增表达式。例如:WHERE created_at >= GETDATE() - 1,优化器能将函数求值一次后转为常量,再配合 created_at 上的索引做范围查找。但以下写法会失效:

  • WHERE datediff(day, created_at, GETDATE()) = 0 —— 函数包裹列,索引失效
  • WHERE YEAR(created_at) = YEAR(GETDATE()) —— 同样破坏 sargability

注意:GETDATE() 是运行时标量函数,每次执行只算一次(非每行),但只要它让谓词不可 SARGable,索引就白搭。

自定义标量函数(UDF)为何比内置函数更危险? SQL Server 2019 之前,UDF 默认被当作“黑盒”,优化器既不内联也不推导其行为,哪怕函数体只有一行 RETURN @x + 1。后果包括:

  • 强制序列化执行(禁用并行)
  • 每行调用一次,且无法复用中间结果(无缓存)
  • 执行计划中显示为 Compute Scalar,但实际开销常被严重低估

SQL Server 2019+ 启用 QUERY_OPTIMIZER_HOTFIXES数据库兼容级别 ≥ 150 后,部分简单 UDF 可自动内联——但必须满足:无副作用、无引用对象、无分支逻辑。否则仍退化为旧模式。

函数代价最隐蔽的地方不在 CPU,而在执行计划失真:你以为只是加了个 len(),结果优化器选了嵌套循环而非哈希连接,最后扫了千万行才出结果。别信“这个函数很简单”,先看执行计划里的 Actual Row countoperator cost 分布。

text=ZqhQzanResources