我目前想知道VARCHAR / NVARCHAR上的一些性能差异,尤其是在使用复杂LIKE查询(以_或%开头)时。
我在Microsoft SQL Server 2014上有一个testsetup。 我有2张桌子。两者都有一个ID字段(标识(1,1)和一个值字段(VARCHAR(450)或NVARCHAR(450))。两者都有相同的1'000'000随机生成的条目。
表名为tblVarCharNoIndex和tblNVarCharNoIndex(因此,没有索引。如果我使用索引,行为几乎相同)。
现在,我执行以下查询测试持续时间(一次在VARCHAR上;一次在NVARCHAR上)
SELECT * FROM tblVarcharNoIndex WHERE Value LIKE '%ab%'
SELECT * FROM tblNVarcharNoIndex WHERE Value LIKE '%ab%'
执行时间大不相同。在VARCHAR表上需要1540ms,在NVARCHAR表上需要8630 ms,因此使用NVARCHAR需要花费超过5倍的时间。
据我所知,NVARCHAR具有性能影响,因为它需要2个字节来存储,这完全有意义。但我无法解释性能下降500%,这对我来说毫无意义。
根据请求,此处还有更多数据。
查询表格创建
CREATE TABLE [dbo].[tblVarcharNoIndex](
[Id] [int] IDENTITY(1,1) NOT NULL,
[Value] [varchar](450) NOT NULL,
CONSTRAINT [PK_tblVarcharNoIndex] PRIMARY KEY CLUSTERED
(
[Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
CREATE TABLE [dbo].[tblNVarcharNoIndex](
[Id] [int] IDENTITY(1,1) NOT NULL,
[Value] [nvarchar](450) NOT NULL,
CONSTRAINT [PK_tblNVarcharNoIndex] PRIMARY KEY CLUSTERED
(
[Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
查询值生成
DECLARE @cnt INT = 0;
DECLARE @entries INT = 1000000 --1'000'000;
DECLARE @maxLength INT = 450;
DECLARE @minLength INT = 50;
DECLARE @value VARCHAR(450)
DECLARE @length INT
WHILE @cnt < @entries
BEGIN
SELECT @value = ''
SET @length = @minLength + CAST(RAND() * (@maxLength - @minLength) as INT)
WHILE @length <> 0
BEGIN
SELECT @value = @value + CHAR(CAST(RAND() * 96 + 32 as INT))
SET @length = @length - 1
END
INSERT INTO tblBase(Value, NValue) VALUES (@value, @value)
SET @cnt = @cnt + 1;
END;
(稍后从tblBase复制值)
LIKE查询问题
DECLARE @start DATETIME
DECLARE @end DATETIME
DECLARE @testname NVARCHAR(100) = 'INSERT FROM other table'
--VARCHAR No Index
PRINT 'starting ''' + @testname + ''' on VARCHAR (No Index)'
SET @start = GETDATE()
SELECT * FROM tblVarcharNoIndex WHERE Value LIKE '%ab%' --This takes 1540ms
SET @end = GETDATE()
PRINT '-- finished ''' + @testname + ''' on VARCHAR (No Index)'
PRINT '-- Duration ' + CAST(DATEDIFF(mcs, @start, @end) AS VARCHAR(100)) + ' microseconds'
--NVARCHAR No Index
PRINT 'starting ''' + @testname + ''' on NVARCHAR (No Index)'
SET @start = GETDATE()
SELECT * FROM tblNVarcharNoIndex WHERE Value LIKE '%ab%' --This takes 8630ms
SET @end = GETDATE()
PRINT '-- finished ''' + @testname + ''' on NVARCHAR (No Index)'
PRINT '-- Duration ' + CAST(DATEDIFF(mcs, @start, @end) AS VARCHAR(100)) + ' microseconds'
执行计划 两个查询的执行计划看起来完全一样(我现在无处上传图像,但它非常简单):
SELECT(0%)&lt; --- Parallelism(Gather Streams)(3%)&lt; ---主键上的聚簇索引扫描(97%)
答案 0 :(得分:4)
如果没有更多数据,将无法为您提供详细信息,从两个查询的执行计划开始。
一些一般原因:
- 正如您所说,扫描时要读取的字节数是原来的两倍
- 页面加载次数将增加
- 必要内存量将增加,这可能导致磁盘操作溢出
- 根据操作系统或SQL设置可能会增加CPU的数量并导致CPU等待。
醇>
答案 1 :(得分:2)
理论虽然合理。 LIKE
是一个运算符,它将每个值与字符串的一部分进行比较。如果运算符真正基于正确且如果SQL Server
没有意识到值的一部分优于另一部分的优点,则SQL Server
必须运行如下的算法(例如{ {1}}):
C#
for (; foundValue == false && Start < (length - 2); Start += 1)
{
searchValue = x.Substring(Start, 2);
if (searchValue == compareValue)
foundValue = true;
}
只有两倍的字符。
从我自己的测试中,我注意到以下几点:
表&#39; tblVarcharNoIndex&#39;。扫描计数1,逻辑读取97,物理 读取0,预读读取0,lob逻辑读取0,lob物理读取 0,lob预读读取0。
表&#39; tblNVarcharNoIndex&#39;。扫描计数1,逻辑读取189,物理 读取0,预读读取0,lob逻辑读取0,lob物理读取 0,lob预读读取0。
逻辑读取意味着比较存储了多少SQL,我们注意到这个数字略高于2倍。我认为在查看实际执行计划时可以看到答案,并注意到估计的行数是56比73,即使最终返回的行数也相同。
然而,查看客户端统计信息可以显示您可能注意到的内容:
NVARCHAR
请注意,从服务器收到的TDS数据包是不同的(回想一下行的估计是不同的),这不仅占用更多的字节而且需要时间来处理。执行时间约为2倍,但处理时间是金额的3倍。
这与处理器与SQL Server协议有多大关系?可能是部分或大部分(这个查询是在一台古老的EDU联想笔记本电脑上运行,配备Windows 10,DuoCore 1.64Ghz,16GB DDR3)。虽然具体细节我没有资格回答。
但是,我们可以得出以下结论: SQL Server对行的估计会对客户端和发送/接收的数据产生影响。
答案 2 :(得分:1)
Unicode比较规则比ascii规则复杂得多。
Unicode数据对性能的影响因各种因素而变得复杂,其中包括:
- Unicode排序规则与非Unicode排序规则之间的区别
- 排序双字节和单字节字符之间的区别
- 客户端和服务器之间的代码页转换
参考:https://msdn.microsoft.com/en-us/library/ms189617.aspx
您可以通过将列的排序规则更改为二进制来确认。
SELECT *
FROM #temp2
where col1 COLLATE Latin1_General_Bin2 like '%str%'
最后,如果您必须使用NVARCHAR并希望提高性能,请考虑一些因素。
答案 3 :(得分:0)
正如其他一些帖子中所建议的那样,在这种情况下对您的性能的最大影响是Unicode比较规则。您可以通过向表中添加带有二进制排序规则的非持久计算字段来解决有关“LIKE”比较查询的问题:
ALTER TABLE tblNVarcharNoIndex
ADD ValueColBin AS UPPER(Value) COLLATE Latin1_General_100_Bin2;
您现在可以查询计算字段,而不是查询持久数据字段。请注意,二进制排序规则区分大小写,因此为了执行不区分大小写的搜索,您必须将搜索字符串转换为大写。以下示例显示更新的查询的外观:
DECLARE @compare NVARCHAR(10) = N'%AB%'
SELECT [Id]
,[Value]
FROM tblNVarcharNoIndex
WHERE [ValueColBin] LIKE @compare collate Latin1_General_100_Bin2
仍会有性能损失,但应该在1.5到2的预期范围内(理论上至少)。请注意,此方法将导致更高的CPU成本。
答案 4 :(得分:0)
使用varchar参数的查询由于列排序规则集而执行索引搜索。
使用nvarchar参数的查询由于列排序规则集而执行索引扫描。
要遵循的基本规则是扫描很糟糕,Seeks很好。
索引扫描
当SQL Server执行扫描时,它会将要从磁盘读取的对象加载到内存中,然后从上到下读取该对象,查找所需的记录。
索引搜寻
当SQL Server执行搜索时,它知道数据将在索引中的哪个位置,因此它从磁盘加载索引,直接转到它需要的索引部分并读取到数据所在的位置它需要结束。这显然是一种比扫描更有效的操作,因为SQL已经知道它所寻找的数据所在的位置。
如何修改执行计划以使用Seek而不是Scan?
当SQL Server正在查找您的数据时,可能会使SQL Server从搜索切换到扫描的最大问题之一是当您要查找的某些列未包含在您希望它使用的索引中时。大多数情况下,这将使SQL Server回退到执行聚簇索引扫描,因为聚簇索引包含表中的所有列。这是最重要的原因之一(至少在我看来)我们现在能够在索引中包含INCLUDE列,而无需将这些列添加到索引的索引列中。通过在索引中包含其他列,我们可以增加索引的大小,但是我们允许SQL Server读取索引,而不必返回聚簇索引,或者自己获取这些值。
<强>参考强>
有关SQL Server执行计划中每个运算符的详细信息,请参阅....