cross apply在top-n场景下常比left join快,因其逐行驱动右侧子查询,配合复合索引可实现高效索引seek;而left join需全表扫描+排序再过滤,易引发性能雪崩。

为什么CROSS APPLY在Top-N场景下常比LEFT JOIN快
因为CROSS APPLY天然支持“逐行驱动右侧子查询”,而LEFT JOIN被迫用窗口函数(如ROW_NUMBER())时,往往要先全表扫描+排序,再过滤——这是典型的“先算后筛”低效模式。
CROSS APPLY取Top-1的执行路径更可控
当右侧是SELECT TOP 1 ... WHERE UserId = u.Id ORDER BY CreatedAt DESC这类语句时:
- 只要
Orders(UserId, CreatedAt DESC)有复合索引,每次都是索引 Seek,不排序、不生成中间结果集 - 总耗时 ≈ 左表行数 × 单次索引查找延迟(毫秒级)
- LEFT JOIN若改写为
INNER JOIN (SELECT *, ROW_NUMBER() OVER (PARTITION BY UserId ORDER BY CreatedAt DESC) rn FROM Orders) o ON ... AND o.rn = 1,则必须先对全部Orders做窗口计算——千万行就扫千万行、排千万行
LEFT JOIN强行模拟Top-N的典型陷阱
常见错误不是语法错,而是逻辑误判导致执行计划崩坏:
-
ROW_NUMBER()必须PARTITION BY左表关联字段,但分区键若无索引,SQL Server可能选哈希匹配甚至临时表,性能雪崩 - 若右表无合适排序索引,
ORDER BY会触发大量Sort操作符,内存压力陡增 - LEFT JOIN无法表达“每行独立Top-N”的语义,硬套会导致重复计算或错误去重(比如用
GROUP BY+MAX()只取值、丢行)
什么时候该换回窗口函数而不是CROSS APPLY
不是所有Top-N都适合CROSS APPLY:
- 要返回完整Top-N行(不止聚合值),且N较大(如Top-50)、分组数少(如仅10个部门),
ROW_NUMBER()+WHERE rn 反而更省JOIN开销 - CROSS APPLY展开后结果集会膨胀(1行变N行),若后续还要
JOIN或GROUP BY,中间行数爆炸可能拖慢整体 - 右侧逻辑含复杂TVF或多次嵌套,执行计划里出现大量“Nested Loops”+“Compute Scalar”,说明逐行调用成本已反超批量处理
(UserId, CreatedAt DESC)这样的覆盖索引,它和LEFT JOIN一样会退化成扫描。别只盯着写法,先看执行计划里的“Index Seek”是不是真出现了。











