为什么查询电话= N'1234'慢于电话='1234'?

Gre*_*Gum 45 sql sql-server query-performance

我有一个varchar字段(20)

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

SELECT * FROM [dbo].[phone] WHERE phone = '5554474477'
Run Code Online (Sandbox Code Playgroud)

但这个很慢(使用索引扫描).

SELECT * FROM [dbo].[phone] WHERE phone = N'5554474477'
Run Code Online (Sandbox Code Playgroud)

我猜测如果我将字段更改为nvarchar,那么它将使用Index Seek.

Mar*_*ith 57

因为nvarchar具有更高的数据类型优先级,varchar所以它需要执行列的隐式转换nvarchar,这会阻止索引搜索.

在某些排序规则下,它仍然可以使用搜索,只需将搜索cast的行匹配到剩余谓词(而不是需要通过扫描对整个表中的每一行执行此操作),但可能您不是使用这样的整理.

整理对此的影响如下所示.使用SQL排序规则时,您将获得扫描,对于Windows排序规则,它会调用内部函数GetRangeThroughConvert并能够将其转换为搜索.

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';
Run Code Online (Sandbox Code Playgroud)

在此输入图像描述

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)))
Run Code Online (Sandbox Code Playgroud)

查询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)
Run Code Online (Sandbox Code Playgroud)

在第二种情况下,计算标量会发出以下值

Expr1004 = 62
Expr1005 = '5554474477'
Expr1006 = '5554474478'
Run Code Online (Sandbox Code Playgroud)

计划中显示的搜索谓词是phone2 > Expr1005 and phone2 < Expr1006这样的,因此它将被排除'5554474477'但标志62意味着它确实匹配.


Joe*_*orn 37

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

从投放NVARCHARVARCHAR是一个缩小的转换.也就是说,NVARCHAR可能存在比类似VARCHAR值更多的信息.NVARCHARVARCHAR输出表示每个输入都是不可能的,因此从前者到后者的输入可能会丢失一些信息.但相反的演员阵容是一个扩大的转换.从VARCHAR值到NVARCHAR值的转换永远不会丢失信息; 这很安全.

这个想法是在提供两种不匹配的类型进行比较时,Sql Server应该总是选择安全转换.这是旧的"正确性胜过表现"的口头禅.或者,用本杰明富兰克林的话来说,"那些为了一点点表现而交易本质正确性的人,既不能正确也不能表现." 因此,类型优先规则旨在确保选择安全转换.

现在你和我都知道你的缩小转换对于这个特定数据也是安全的,但是Sql Server查询优化器并不关心这一点.无论好坏,它在构建执行计划时遵循数据类型信息并遵循类型优先规则.

这是真正的踢球者:现在我们正在进行这个演员,我们必须为表格中的每一行做这个.即使对于不会与比较滤波器匹配的行也是如此.更重要的是,列中的强制转换值不再与存储在索引中的值相同,因此列上的任何索引现在对于此查询都毫无价值.

我认为你很幸运能够获得这个查询的索引扫描,而不是全表扫描,这可能是因为有一个覆盖索引可以满足查询的需要(优化器可以选择转换所有记录在索引中就像表中的所有记录一样容易).

您可以通过以更有利的方式明确解决类型不匹配来修复此查询的内容.当然,实现这一目标的最佳方法是VARCHAR首先提供平原,并且完全不需要进行铸造/转换:

SELECT * FROM [dbo].[phone] WHERE phone = '5554474477'
Run Code Online (Sandbox Code Playgroud)

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

SELECT * FROM [dbo].[phone] WHERE phone = cast(N'5554474477' as varchar(20))
Run Code Online (Sandbox Code Playgroud)

这两个示例都有利地解决了原始代码的类型不匹配问题.即使在后一种情况下,您可能对文字的控制力比您所知.例如,如果此查询是从.Net程序创建的,则问题可能与该AddWithValue()函数有关.我过去曾写过这个问题以及如何正确处理它.

这些修复程序也有助于说明事情是这样的.

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

但是,我发现他们不可能做到这一点.在我看来,相对于完成单个查询评估的额外性能成本以及理解优化器正在做什么的复杂性,现有系统中查询的更正太容易了("为什么服务器不遵循记录的优先级规则在这里?")为它辩护.


Der*_*ğlu 7

 SELECT * FROM [dbo].[phone] WHERE phone = N'5554474477'
Run Code Online (Sandbox Code Playgroud)

被解释为

 SELECT * from [dbo].[phone] WHERE CAST(phone as NVARCHAR) = N'5554474477'
Run Code Online (Sandbox Code Playgroud)

这会阻止索引使用