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
相关产品推荐
相关产品推荐

