为什么用phone = N'1234'的查询要比phone ='1234'慢?

时间:2019-02-05 18:35:25

标签: sql sql-server query-performance

我有一个字段是varchar(20)

执行此查询时,它很快(使用索引查找):

SELECT * FROM [dbo].[phone] WHERE phone = '5554474477'

但是这一步很慢(使用索引扫描)。

SELECT * FROM [dbo].[phone] WHERE phone = N'5554474477'

我猜测如果将字段更改为nvarchar,则它将使用索引查找。

3 个答案:

答案 0 :(得分:57)

由于nvarchar的{​​{3}}比varchar高,因此它需要对该列执行隐式强制转换为nvarchar,这会阻止索引查找。

在某些归类中,它仍然可以使用搜索,而仅将cast推入针对与搜索匹配的行的残差谓词中(而不是需要通过a来对整个表中的每一行都这样做)扫描),但大概不是在使用这种排序规则。

排序规则对此的影响如下所示。使用SQL排序规则时,您会进行扫描,对于Windows排序规则,它将调用内部函数datatype precedence并将其转换为查找。

CREATE TABLE [dbo].[phone]
  (
     phone1 VARCHAR(500) COLLATE sql_latin1_general_cp1_ci_as CONSTRAINT uq1 UNIQUE,
     phone2 VARCHAR(500) COLLATE latin1_general_ci_as CONSTRAINT uq2 UNIQUE,
  );

SELECT phone1 FROM [dbo].[phone] WHERE phone1 = N'5554474477';
SELECT phone2 FROM [dbo].[phone] WHERE phone2 = N'5554474477';

GetRangeThroughConvert

SHOWPLAN_TEXT在下面

查询1

  |--Index Scan(OBJECT:([tempdb].[dbo].[phone].[uq1]),  WHERE:(CONVERT_IMPLICIT(nvarchar(500),[tempdb].[dbo].[phone].[phone1],0)=CONVERT_IMPLICIT(nvarchar(4000),[@1],0)))

查询2

  |--Nested Loops(Inner Join, OUTER REFERENCES:([Expr1005], [Expr1006], [Expr1004]))
       |--Compute Scalar(DEFINE:(([Expr1005],[Expr1006],[Expr1004])=GetRangeThroughConvert([@1],[@1],(62))))
       |    |--Constant Scan
       |--Index Seek(OBJECT:([tempdb].[dbo].[phone].[uq2]), SEEK:([tempdb].[dbo].[phone].[phone2] > [Expr1005] AND [tempdb].[dbo].[phone].[phone2] < [Expr1006]),  WHERE:(CONVERT_IMPLICIT(nvarchar(500),[tempdb].[dbo].[phone].[phone2],0)=[@1]) ORDERED FORWARD)

在第二种情况下,计算标量enter image description here

Expr1004 = 62
Expr1005 = '5554474477'
Expr1006 = '5554474478'

计划中显示的搜索谓词位于phone2 > Expr1005 and phone2 < Expr1006上,因此从表面上看将排除'5554474477',但是标志62表示此匹配。

答案 1 :(得分:37)

其他答案已经解释了会发生什么;我们已经看到NVARCHAR的类型优先级比VARCHAR高。我想说明为什么,即使第二个选项很明显,数据库也必须将列的每一行都强制转换为NVARCHAR,而不是将单个提供的值强制转换为VARCHAR。在直观和经验上都快得多。

NVARCHARVARCHAR的投放是缩小的转化。也就是说,NVARCHAR比类似的VARCHAR值具有更多的信息。不可能用NVARCHAR输出来表示每个VARCHAR输入,因此从前者到后者的转换可能会丢失一些信息。但是相反的转换是扩大转换。从VARCHAR值转换为NVARCHAR值不会丢失任何信息;是安全

这个想法是当出现两个不匹配的类型进行比较时,Sql Server应该始终选择安全的转换。这是旧的“正确性胜过性能”的口头禅。或者,用Benjamin Franklin来解释:“以基本的正确性换取一点表现的人既不应该正确也不应该表现。”然后,类型优先级规则旨在确保选择安全的转换。

现在,我俩都知道您的缩小转换对于这种特定数据也是安全的,但是Sql Server查询优化器对此并不在意。不管是好是坏,它在构建执行计划时都会首先查看数据类型信息,并遵循类型优先规则。

这才是真正的关键:现在我们要进行演员表转换,我们必须对表中的每一行进行操作。即使对于不匹配比较过滤器的行也是如此。此外,列中的转换值不再与索引中存储的值相同,因此该查询中列上的任何索引现在都不值钱

我认为您很幸运对此查询进行索引扫描,而不是全表扫描,这很可能是因为有一个涵盖性的索引可以满足查询的需求(优化程序可以选择强制转换索引中的所有记录,就像表中的所有记录一样。)

您可以通过以更有利的方式显式解决类型不匹配的问题来解决此查询的问题。当然,最好的方法是首先提供一个简单的VARCHAR并完全避免进行任何强制转换/转换:

SELECT * FROM [dbo].[phone] WHERE phone = '5554474477'

但是我怀疑我们看到的是应用程序提供的值,您不必控制该部分文字。如果是这样,您仍然可以这样做:

SELECT * FROM [dbo].[phone] WHERE phone = cast(N'5554474477' as varchar(20))

任何一个示例都可以很好地解决原始代码与类型不匹配的问题。即使出现后一种情况,您对文字的控制也可能比您所知道的要多。例如,如果此查询是从.Net程序创建的,则问题可能与AddWithValue()函数有关。 I've written about this issue in the past以及如何正确处理。

这些修复程序还有助于说明为什么事情是这样的。

在将来的某个时候,Sql Server开发人员可能会增强查询优化器,以查看类型优先级规则导致每行转换导致表或索引扫描的情况,但相反的转换涉及常量数据,并且可能只是索引查找,在这种情况下,请先查看数据以查看它是否也安全。

但是,我发现他们不太可能这样做。我认为,相对于完成单个查询的评估所需的额外性能成本以及了解优化器正在执行的操作的复杂性而言,现有系统中查询的更正太容易了(“为什么服务器不遵循记录的优先级规则在这里?”)来证明这一点。

答案 2 :(得分:7)

 SELECT * FROM [dbo].[phone] WHERE phone = N'5554474477'

被解释为

 SELECT * from [dbo].[phone] WHERE CAST(phone as NVARCHAR) = N'5554474477'

防止索引使用