在T-SQL中使用CAST降低性能


12

我们有一个SQL生成器,该生成器通常针对指定的字段发出SQL条件语句(为了便于讨论,我们将其标记为myField)。

如果myField类型为NVARCHAR,我们可以将所述字段与字符串进行比较,如下所示:myField = 'foo'

但是,这不适用于type的字段NTEXT。因此,我们必须使用强制转换进行比较 CAST(myField as NVARCHAR(MAX)) = 'foo'。如果myField类型为NVARCHAR或,则实际上可以使用NTEXT

已经是类型的字段上执行上述强制转换会对性能产生什么影响NVARCHAR?我希望SQL Server足够聪明,可以动态地识别myField已经存在的类型NVARCHAR(有效地将其CAST变成无操作)。


对于任何发现此问题的人的简要说明:NTEXT(以及TEXT和IMAGE)已正式弃用,并且由于在某些将来版本的SQL Server中将被删除(尽管IIRC仍可在SQL1014中使用),因此您应该使用NVARCHR(MAX) (或VARCHAR(MAX)或VARBINARY(MAX))代替。在这种情况下,用NVARCHAR(MAX)替换NTEXT列将消除对强制类型转换的需要,因为可以直接使用该类型进行比较,并且在此处和其他地方也可以得到其他潜在的效率提高。不幸的是,您不能为*(MAX)列建立索引,但也不能为TEXT / NTEXT索引。
David Spillett 2014年

Answers:


12

如果该列的类型强制转换为完全相同的数据类型和长度,而seek谓词是一个文字,则确实确实忽略了它或将其视为no-op,并且对相等性进行了索引查找。

Seek Keys[1]: Prefix: [tempdb].[dbo].[#test].name = Scalar Operator(N'rpc')

如果列的强制类型转换为相同的数据类型但长度更大,并且查找谓词是字符串文字,则会导致索引扫描。这显然是可以避免的。

如果列的类型转换为相同的数据类型且长度相同或更大,并且查找谓词是局部变量,则它将计算标量运算符添加到执行计划中。这将调用GetRangeThroughConvert并输出一个范围。

该范围用于进行索引查找,看起来非常有效

Seek Keys[1]: 
Start: [tempdb].[dbo].[#test].name > Scalar Operator([Expr1006]), 
End: [tempdb].[dbo].[#test].name < Scalar Operator([Expr1007])

测试代码

SELECT *
 INTO #test
  FROM [master].[dbo].[spt_values]

CREATE NONCLUSTERED INDEX [ixname] ON #test
(
    [name] ASC
)

DECLARE @name NVARCHAR(MAX)

SET @name = 'rpc'

SELECT name
FROM #test
WHERE CAST(name AS NVARCHAR(35))= @name --Cast the same and local variable

SELECT name
FROM #test
WHERE CAST(name AS NVARCHAR(MAX))=@name --Cast to longer and local variable

SELECT name
FROM #test
WHERE CAST(name AS NVARCHAR(35))='rpc' --Cast the same and literal

SELECT name
FROM #test
WHERE CAST(name AS NVARCHAR(MAX))='rpc' --Cast to longer and literal

6

总的来说,这CAST会破坏性能,因为如马丁·史密斯的最后一个例子所示,这会使索引查找的任何使用无效。强制转换为nvarchar(max)或转换为不同的长度意味着不同的数据类型:事实并非如此nvarchar

最重要的是,比较右侧的数据类型也很重要。如果它是长度不同的局部变量或参数,则一侧将隐式CAST包含在2个数据类型中的最大宽度(请参阅datatype priority)。

基本上,如果您有将军CASTnvarchar(max)话,就会把事情搞砸了。ntext在添加之前,我会考虑固定使用CAST

转换可能不会显示在查询计划中。参见Paul White的博客文章


2

只是说明一下,在这样的情况下,其中Datecreated为datetime的转换

 Cast (Datecreated as date) = cast(@MydatetimeValue as date)

如果存在索引,并且不存在索引,则不会破坏SQL使用索引的能力,可能会导致记录丢失的索引。

同样,如果从优化器知道强制转换操作不会更改2种可比较数据类型的排序顺序,则从函数转换inttinyint或转换bigintintetc时,转换函数不会阻止SQL使用索引。

这是一堆测试,您可以使用Adventureworks2008R2运行并查看实际计划

select count(*) from Sales.SalesOrderDetail where SalesOrderID = 8 --1
select top 10 * from Sales.SalesOrderDetail where cast(SalesOrderID as tinyint) = 8  --2
select top 10 * from Sales.SalesOrderDetail where cast(SalesOrderID as bigint) = 8  --3
select top 10 SalesOrderID from Sales.SalesOrderDetail where cast(ModifiedDate  as date) = '19780322' --4
select top 10 SalesOrderID from Sales.SalesOrderDetail where convert(date,ModifiedDate) = '19780322'  --5
select top 10 SalesOrderID from Sales.SalesOrderDetail where cast(ModifiedDate as varchar(20)) = '1978'  --6 -- THIS WILL NOT USE INDEX
select  SalesOrderID from Sales.SalesOrderDetail where cast(ModifiedDate  as date) between '19780101' and '19780109'  --7

1
强制转换为日期可以执行索引查找,但与不强制强制转换为范围查找相比仍然存在问题
马丁·史密斯
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.