编译器在like子句中搜索wild char'%%'的成本

时间:2011-12-05 08:05:23

标签: sql sql-server sql-server-2008

我有一个动态存储过程,必须在SQL Server 2008的case语句中动态添加where子句。

我的程序如下: -

CREATE PROCEDURE SPGETDATA
@STRNAME NVARCHAR(100),
@STRCODE NVARCHAR(100)

AS
BEGIN
SELECT  myTable.*
FROM myTable
WHERE 
IsDELETED = 0  
AND STRNAME LIKE CASE WHEN (RTRIM(LTRIM(@STRNAME))) <> '' THEN 
'%'+ @STRNAME + '%' ELSE '%%' END 
AND STRCODE LIKE CASE WHEN (RTRIM(LTRIM(@STRCODE)) <> '') THEN 
'%' + @STRCODE + '%'  ELSE '%%' END** 

END

用户可以选择@strname或@strcode。但不是一次两个。

在这种情况下,一个类似的声明是可以的,但替代方案始终是查询的负担,因为它总是如

@STRNAME like '%%'

或如下

@STRCODE like '%%'

现在,如果我使用这种方法,编译器会花费一些时间来搜索'%%',即使没有什么可匹配的,还是会绕过它并且不需要任何费用?我也检查了执行计划,但它没有为like子句显示任何内容。

因此我必须在webApps中使用它,因此sp的速度必须考虑。该表有数百万行。

两者的执行计划都是相同的。如果我在查询中使用类似cluase或从查询中删除它显示 - Clustred index sacn 100%。

请帮忙。

3 个答案:

答案 0 :(得分:2)

首先,如果其中一列可以为空,则测试col LIKE '%%'不是No-Op,但实际上相当于测试列col IS NOT NULL,这可能不是所需的效果。

其次,如果列不可为空,那么它确实是No-Op,那么这不是一个特别好的方法,因为SQL Server将优化检查。有关更好的方法,请参阅Dynamic Search Conditions in T-SQL

在你的情况下,机会不会有太大的不同。

由于您总是使用针对一个或另一个列的前导通配符进行搜索而您正在进行SELECT *,因此您最终可能会进行全表扫描。

可能会产生影响的一种情况是,您可以扫描一个或多个列的索引较窄,而不是扫描整个聚簇索引/表。即便如此,SQL Server仍然需要进行书签查找以检索*,因此可以非常便宜地为该特定查询评估残差谓词。

但是,生成的计划完全不适合使用其他参数进行调用,因此这次尝试捕获所有查询可能会给您一个参数嗅探问题,如下所示。

测试表

CREATE TABLE myTable
(
id int primary key,
STRNAME VARCHAR(100) NOT NULL,
STRCODE VARCHAR(100) NOT NULL,
IsDELETED BIT NOT NULL DEFAULT 0,
Filler CHAR(7000) NULL,
)
INSERT INTO myTable(id, STRNAME, STRCODE)
SELECT ROW_NUMBER() OVER (ORDER BY (SELECT 0)), 
       ISNULL(name,type), 
       ISNULL(name,type)
FROM master..spt_values

CREATE INDEX ix ON myTable(STRNAME)

首先使用name参数调用(扫描计数1,逻辑读取7)

EXEC sp_executesql N'
SELECT  myTable.*
FROM myTable
WHERE 
IsDELETED = 0  
AND STRNAME LIKE CASE WHEN (RTRIM(LTRIM(@STRNAME))) <> '''' THEN 
''%''+ @STRNAME + ''%'' ELSE ''%%'' END 
AND STRCODE LIKE CASE WHEN (RTRIM(LTRIM(@STRCODE)) <> '''') THEN 
''%'' + @STRCODE + ''%''  ELSE ''%%'' END 
', 
N'@STRNAME NVARCHAR(100),
@STRCODE NVARCHAR(100)
', @STRNAME = '(rpc)', @STRCODE=''

调用代码参数重用相同的计划(扫描计数1,逻辑读取7690)

EXEC sp_executesql N'
SELECT  myTable.*
FROM myTable
WHERE 
IsDELETED = 0  
AND STRNAME LIKE CASE WHEN (RTRIM(LTRIM(@STRNAME))) <> '''' THEN 
''%''+ @STRNAME + ''%'' ELSE ''%%'' END 
AND STRCODE LIKE CASE WHEN (RTRIM(LTRIM(@STRCODE)) <> '''') THEN 
''%'' + @STRCODE + ''%''  ELSE ''%%'' END 
', 
N'@STRNAME NVARCHAR(100),
@STRCODE NVARCHAR(100)
', @STRNAME = '', @STRCODE='(rpc)'

使用代码参数调用生成特定计划(扫描计数1,逻辑读取2517)

EXEC sp_executesql N'
SELECT  myTable.*
FROM myTable
WHERE 
IsDELETED = 0  
AND STRNAME LIKE CASE WHEN (RTRIM(LTRIM(@STRNAME))) <> '''' THEN 
''%''+ @STRNAME + ''%'' ELSE ''%%'' END 
AND STRCODE LIKE CASE WHEN (RTRIM(LTRIM(@STRCODE)) <> '''') THEN 
''%'' + @STRCODE + ''%''  ELSE ''%%'' END 
OPTION (RECOMPILE)
', 
N'@STRNAME NVARCHAR(100),
@STRCODE NVARCHAR(100)
', @STRNAME = '', @STRCODE='(rpc)'

答案 1 :(得分:1)

你可以这样写:

IF COALESCE(RTRIM(LTRIM(@STRNAME))), '') <> '' 
BEGIN
  SELECT  myTable.*
  FROM myTable
  WHERE IsDELETED = 0  
    AND STRNAME LIKE '%'+ @STRNAME + '%'
END
ELSE -- IF COALESCE(RTRIM(LTRIM(@STRCODE))), '') <> '' 
BEGIN
  SELECT  myTable.*
  FROM myTable
  WHERE IsDELETED = 0  
    AND STRCODE LIKE '%'+ @STRCODE + '%'
END

无论如何,使用LIKE会阻止DB使用索引。这将是您最高的执行成本。

答案 2 :(得分:0)

我会创建两个版本的查询,一个用LIKE '%%',另一个没有,然后比较他们的执行计划。