使用CONCAT表达式的SELECT语句性能问题:无需可见计算列的优化方案
解决方案
方案1:使用SQL Server隐藏计算列(2016及以上版本支持)
你提到计算列无法使用HIDDEN属性,但实际上SQL Server 2016及以后版本允许给持久化计算列标记为隐藏,这样该列不会在SELECT *或常规表结构查询中显示。修改你的计算列创建语句即可:
ALTER TABLE [MyTable] ADD COL_ABC AS (CONCAT([COL_A],[COL_B],[COL_C])) PERSISTED HIDDEN;
之后在这个隐藏列上创建索引,既能保留性能提升,又不会让该列暴露给普通查询。
方案2:重写查询语句,利用现有复合索引
原来的查询中,CONCAT函数包裹列的写法会让查询失去sargable特性,导致SQL Server无法利用你已有的COL_A, COL_B, COL_C复合索引进行范围扫描和排序。通过拆分CONCAT的条件,我们可以让查询重新适配索引:
首先把你的目标字符串'OA11 50-199 XZ'按三列的长度拆分:
- 目标COL_A值:
LEFT('OA11 50-199 XZ', 5)→'OA11 '(匹配nchar(5)的长度,注意包含空格) - 目标COL_B值:
SUBSTRING('OA11 50-199 XZ', 6, 8)→' 50-199'(匹配nchar(8)的长度) - 目标COL_C值:
SUBSTRING('OA11 50-199 XZ', 14, 4)→' XZ'(匹配nchar(4)的长度)
然后重写WHERE和ORDER BY子句:
SELECT * FROM [MyTable] WHERE COL_A > 'OA11 ' OR (COL_A = 'OA11 ' AND COL_B > ' 50-199') OR (COL_A = 'OA11 ' AND COL_B = ' 50-199' AND COL_C >= ' XZ') ORDER BY COL_A ASC, COL_B ASC, COL_C ASC;
这个查询完全适配你的复合索引,SQL Server会直接利用索引完成范围过滤和排序,性能和使用计算列索引一致,且不需要修改原表结构。另外,ORDER BY COL_A, COL_B, COL_C的结果和原ORDER BY CONCAT(...)完全一致,因为拼接顺序和索引顺序完全匹配。
方案3:使用索引视图(不修改原表)
如果不想对原表做任何修改,可以创建一个带计算列的索引视图,原表不会新增任何列,查询时通过视图获取数据:
-- 创建绑定到原表的视图,必须明确列出所有需要的列(SCHEMABINDING要求) CREATE VIEW vw_MyTable_ABC WITH SCHEMABINDING AS SELECT COL_A, COL_B, COL_C, -- 替换为原表的其他所有列,或者只列出你需要查询的列 COL_D, COL_E, CONCAT(COL_A, COL_B, COL_C) AS COL_ABC FROM dbo.MyTable; -- 索引视图必须创建唯一聚集索引 CREATE UNIQUE CLUSTERED INDEX IX_vw_MyTable_ABC ON vw_MyTable_ABC (COL_ABC);
查询时直接使用该视图:
-- SQL Server标准版需要加WITH (NOEXPAND)提示才能使用索引视图的索引 SELECT * FROM vw_MyTable_ABC WITH (NOEXPAND) WHERE COL_ABC >= 'OA11 50-199 XZ' ORDER BY COL_ABC ASC;
企业版SQL Server会自动优化视图查询,无需额外提示。
内容的提问来源于stack exchange,提问作者nk kellner
相关产品推荐
相关产品推荐

