cross apply 变慢主因是右侧多语句tvf未内联导致逐行执行、无法下推谓词及索引失效;应改用内联tvf、添加覆盖索引并检查执行计划与函数统计信息。

CROSS APPLY 查询慢,大概率不是语法问题,而是右侧表值函数(TVF)没走索引或被当成多行执行器反复调用。
为什么 CROSS APPLY 会变慢?
核心在于“逐行调用”这个特性:SQL Server 对左侧每一行,都重新执行一次右侧的 SELECT 或表值函数。如果右侧是未内联的多语句 TVF(CREATE FUNCTION ... RETURNS @t TABLE (...) AS BEGIN ... END),它会被当作黑盒,无法展开优化,且每次调用都生成独立执行计划 —— 这比 JOIN 慢一个数量级。
- 多语句 TVF 无法下推谓词,WHERE 条件进不去函数体内部
- 即使函数里有
WHERE id = @param,SQL Server 仍可能对全表扫描后再过滤 - 右侧子查询若含
TOP+ORDER BY,但没建好索引,每行都触发一次排序 - 左侧数据量大(比如 10 万行),右侧哪怕平均 10ms,总耗时也超 16 分钟
怎么判断右侧是不是“坏 TVF”?
看执行计划里右侧操作符的“实际行数”和“估计行数”是否严重偏差,以及是否有黄色感叹号(缺失索引)或红色叉号(隐式转换)。更直接的方法是单独执行该 TVF:
SELECT * FROM dbo.SplitTags('A,B,C')
如果返回慢、或执行计划显示“Table Scan”或“Clustered Index Scan”,基本可以确认问题出在它身上。
- 用
sys.dm_exec_function_stats查看该函数缓存计划的平均 CPU/逻辑读:total_logical_reads / execution_count超过 1000 就要警惕 - 检查函数定义:含
BEGIN ... END、INSERT INTO @t、循环或临时表的,基本是多语句 TVF,应重写为内联 TVF - 内联 TVF 长这样:
RETURNS TABLE AS RETURN (SELECT ...),它会被优化器展开,能用上索引
如何把慢 Cross Apply 变快?
优先替换右侧为内联表值函数,再辅以索引和参数化。别试图“优化 APPLY 本身”——它的机制就是逐行,优化点永远在右侧。
- 把多语句 TVF 改成内联 TVF:去掉
BEGIN/END和表变量,直接RETURN (SELECT ...) - 确保内联 TVF 中的 WHERE 条件列有索引,特别是被左侧传入的参数列(如
WHERE OrderID = @order_id) - 如果右侧是子查询,把
ORDER BY ... OFFSET 0 ROWS FETCH NEXT 3 ROWS ONLY改成带覆盖索引的TOP 3,并确认排序字段已索引 - 避免在 CROSS APPLY 右侧调用标量函数(如
dbo.CalcValue(x)),它会在每行重复计算;改用计算列 + 索引,或提前在左侧 JOIN 前算好
容易被忽略的性能陷阱
很多人以为加了索引就万事大吉,但 CROSS APPLY 的右侧执行环境很特殊:它不继承外部查询的参数嗅探上下文,尤其在存储过程中,第一次编译的参数值可能让计划完全失效。
- 用
OPTION (RECOMPILE)强制每次重编译(适合参数变化大、数据分布不均的场景) - 避免在 TVF 内部用
GETDATE()或NEWID(),它们会让函数无法内联 - 如果左侧是大表分页(如
OFFSET 10000 ROWS),CROSS APPLY 会先取全部左侧行再关联,此时应把分页逻辑移到最外层,而不是包在 APPLY 里 - 注意统计信息陈旧:右侧表若长期没更新统计信息,优化器可能误判行数,导致选错连接方式(如 Nested Loop 而非 Hash Join)
真正卡住性能的,往往不是 APPLY 关键字本身,而是右侧那个看似无害的函数——它像一个黑箱,把索引、参数、统计信息全挡在外面。动手前,先用执行计划和 sys.dm_exec_function_stats 把它打开看看。










