You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

CROSS APPLY查询性能问题求助:慢查询优化及索引咨询

优化慢查询的几个关键步骤

咱们一步步拆解并优化这个查询,先揪出几个拖慢性能的关键点:

1. 移除完全冗余的GROUP BY子句

你的CROSS APPLY已经通过TOP 1为每个city_id返回了唯一的一行数据(最早创建且名称排序最前的酒店),后续的GROUP BY完全是多余的——它不会改变查询结果,只会让数据库做额外的分组计算,平白增加性能开销。直接删掉这部分就能省掉不少无用功。

优化后的基础查询可以简化成这样:

SELECT A.city_id, FIRST_Hotel_By_City.hotel_name 
FROM hotel AS A 
CROSS APPLY ( 
    SELECT TOP 1 I.hotel_name 
    FROM hotel AS I 
    WHERE A.city_id = I.city_id 
    ORDER BY I.created_date ASC, hotel_name ASC 
) FIRST_Hotel_By_City 
ORDER BY A.city_id

(我还把SELECT TOP 1 I.*改成了只选需要的hotel_name,减少不必要的数据传输量)

2. 把索引改成真正的覆盖+有序索引

你现有的索引ix_cityid只把city_id作为键列,created_date和hotel_name放在INCLUDE里,但其实可以利用索引的有序性彻底避免查询时的排序操作。推荐创建这样的索引:

CREATE NONCLUSTERED INDEX ix_cityid_createddate_hotelname 
ON hotel(city_id, created_date, hotel_name);

这个索引的优势非常明显:

  • 按city_id分组,能直接定位每个城市的所有酒店
  • 每个城市内的酒店已经按created_date ASC, hotel_name ASC排好序了,TOP 1可以直接取第一条,不需要额外做排序运算
  • 索引包含了查询需要的所有列,数据库不需要回表查询原数据,直接从索引就能拿到结果——这就是覆盖索引,能大幅提升查询速度

如果原索引已经存在,可以用DROP_EXISTING=ON参数直接替换:

CREATE NONCLUSTERED INDEX ix_cityid_createddate_hotelname 
ON hotel(city_id, created_date, hotel_name)
WITH (DROP_EXISTING = ON);

3. 可选:用窗口函数替换CROSS APPLY(写法更简洁,性能可能更优)

对于这种「分组取TOP1」的场景,用ROW_NUMBER()窗口函数的写法有时候会生成更高效的执行计划,逻辑也更直观:

SELECT city_id, hotel_name
FROM (
    SELECT 
        city_id, 
        hotel_name,
        -- 按城市分组,按创建时间和酒店名称排序,给每行标记行号
        ROW_NUMBER() OVER (PARTITION BY city_id ORDER BY created_date ASC, hotel_name ASC) AS rn
    FROM hotel
) AS ranked_hotels
WHERE rn = 1 -- 只保留每个城市的第一行数据
ORDER BY city_id;

这个查询和原查询逻辑完全一致,但在大数据量下,窗口函数的执行计划可能会比CROSS APPLY的关联操作更高效,尤其是配合上面创建的索引时,数据库可以直接利用索引的有序性计算行号,不需要额外的排序或关联步骤。


内容的提问来源于stack exchange,提问作者SqlGeek

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.22 08:56:44