CROSS APPLY不是性能优化语法,而是解决右侧依赖左表字段等特定语义问题的必要工具;强行用它替代JOIN反而降低性能,其性能瓶颈在于右侧子查询是否可优化、索引是否合理及函数类型是否恰当。

不能靠“用CROSS APPLY替代JOIN”来提高效率——它不是性能优化语法,而是解决特定语义问题的必要工具。强行替换反而会拖慢查询。
什么时候必须用 CROSS APPLY,而不是 JOIN
当右侧表达式依赖左表字段且无法静态化时,JOIN 直接报错,CROSS APPLY 是唯一合法写法:
-
CROSS APPLY dbo.SplitTags(u.Tags):函数参数u.Tags来自左表,JOIN 的 ON 子句不允许引用右端子查询里的左表列 -
CROSS APPLY (SELECT TOP 1 * FROM Orders o WHERE o.UserId = u.Id ORDER BY CreatedAt DESC):每行需独立取最新订单,JOIN 无法在 FROM 中实现这种“逐行动态 TOP” -
CROSS APPLY OPENJSON(u.ConfigJson)或CROSS APPLY STRING_SPLIT(u.Roles, ','):输入随左表每行变化,右端根本不是一张“表”
错误写法:FROM Users u INNER JOIN (SELECT * FROM Cities WHERE RegionId = u.RegionId) c ON 1=1 —— SQL Server 报错 The multi-part identifier "u.RegionId" could not be bound.
CROSS APPLY 右侧写子查询的性能雷区
真正拖慢查询的不是 CROSS APPLY 关键字本身,而是右侧子查询的执行方式:
- 没建覆盖索引:例如
WHERE o.UserId = u.Id ORDER BY o.CreatedAt DESC,必须有(UserId, CreatedAt DESC) INCLUDE (OrderId, Amount),否则每行都触发一次全索引扫描 - 误用
OFFSET 0 ROWS FETCH NEXT 1 ROWS ONLY:它无法走索引 Seek,强制 Sort;换成TOP 1并配好索引才有效 - 右侧含标量函数:如
dbo.CalcScore(o.Amount),会在左侧每行重复执行;应提前计算或改用计算列 - 左侧大表 + 右侧无索引子查询:10 万行 × 每次 20ms = 总耗时超 33 分钟
如何判断右侧表值函数是否拖累性能
别猜,直接查:
- 单独执行函数:
SELECT * FROM dbo.GetOrderDetails(123),看是否慢、执行计划是否出现Table Scan - 查缓存统计:
SELECT total_logical_reads / execution_count FROM sys.dm_exec_function_stats WHERE object_id = OBJECT_ID('dbo.GetOrderDetails'),结果 > 1000 就得重写 - 看函数定义:含
BEGIN ... END、INSERT INTO @t、循环或临时表的,基本是多语句 TVF(MSTVF),必须重写为内联 TVF:RETURNS TABLE AS RETURN (SELECT ... WHERE Id = @param)
内联 TVF 能被优化器展开,像写在主查询里一样走索引;MSTVF 则被当黑盒,每次调用都生成新计划、无法下推谓词。
OUTER APPLY 和 LEFT JOIN 的关键区别在哪
表面相似,但能力边界不同:
-
LEFT JOIN要求右端是静态关系(如一张物理表、CTE 或物化视图),不能含对左表列的直接引用 -
OUTER APPLY允许右端是动态计算,比如OUTER APPLY (SELECT TOP 1 * FROM Logs l WHERE l.UserId = u.Id ORDER BY Time DESC),这种“每行取最新日志”LEFT JOIN 写不出来 - 若右侧是 MSTVF,
OUTER APPLY仍会逐行调用;而试图用LEFT JOIN + ROW_NUMBER()改写,可能引入额外排序和临时结果集,代码更长且未必更快
真正容易被忽略的是:CROSS/OUTER APPLY 的性能问题几乎从不来自 APPLY 本身,而是右侧表达式是否可优化、是否被正确索引、是否用了不该用的函数类型。

















