为啥“nvarchar”参数比“文本”“SqlCommand”命令的其他类型更快?

Posted

技术标签:

【中文标题】为啥“nvarchar”参数比“文本”“SqlCommand”命令的其他类型更快?【英文标题】:Why are `nvarchar` parameters faster than other types for 'text' `SqlCommand` commands?为什么“nvarchar”参数比“文本”“SqlCommand”命令的其他类型更快? 【发布时间】:2016-06-30 01:15:42 【问题描述】:

概述

这个问题是这个问题的更具体的版本:

sql server - performance hit when passing argument of C# type Int64 into T-SQL bigint stored procedure parameter

但我注意到其他数据类型的性能同样受到影响(事实上,就我而言,我根本没有使用任何 bigint 类型)。

这里有一些其他问题,似乎应该涵盖这个问题的答案,但我观察到的与他们所指出的相反:

c# - When should "SqlDbType" and "size" be used when adding SqlCommand Parameters? - Stack Overflow .net - What's the best method to pass parameters to SQLCommand? - Stack Overflow

上下文

我有一些用于将数据插入表的 C# 代码。代码本身是数据驱动的,因为其他一些数据指定了应该插入数据的目标表。所以,虽然我可以在存储过程中使用动态 SQL,但我选择在我的 C# 应用程序中生成动态 SQL。

我插入的行的命令文本总是相同的,所以我在插入任何行之前生成一次。命令文本的格式为:

INSERT SomeSchema.TargetTable ( Column1, Column2, Column3, ... )
VALUES ( SomeConstant, @p0, @p1, ... );

对于每个插入,我创建一个 SqlParameter 对象数组。

对于 'nvarchar' 行为,我只是使用 SqlParameter(string parameterName, object value) 构造方法,并没有显式设置任何其他属性。

对于“退化”行为,我使用了 SqlParameter(string parameterName, SqlDbType dbType) 构造函数方法,并根据需要设置了 SizePrecisionScale 属性。

对于这两个版本的代码,传递给构造函数方法或单独分配给Value 属性的值的类型为object

代码的“nvarchar”版本大约需要 1-1.5 分钟。 “退化”或“特定类型”代码耗时超过 9 分钟;所以慢了 6-9 倍。

SQL Server Profiler 没有发现任何明显的罪魁祸首。特定于类型的代码正在生成看起来更好的 SQL,即动态 SQL 命令,其参数包含适当的数据类型和类型信息。

假设

我怀疑,因为我将 object 类型值作为参数值传递,ADO.NET SQL Server 客户端代码在生成命令并将命令发送到 SQL Server 之前正在强制转换、转换或以其他方式验证该值.我很惊讶从 nvarchar 到 SQL Server 必须执行的每个相关目标表列类型的转换比客户端代码执行的任何操作都快得多。

注意事项

我知道SqlBulkCopy 可能是插入大量行的最佳选择,但我更好奇为什么“nvarchar”的情况优于“特定类型”的情况,并且考虑到它通常处理的数据量,我当前的代码足够快。

【问题讨论】:

您是否还比较了此类半散装刀片的性能? INSERT SomeSchema.TargetTable ( Column1, Column2, Column3, ... ) values ( SomeConstant, @p0, @p1, ... ), ( SomeConstant, @p0, @p1, ... ), ( SomeConstant, @p0, @p1, ... ), ( SomeConstant, @p0, @p1, ... ); 就我个人而言,我注意到最好的性能是一次 500kb 到 800kb 的块。 @Ralph 我没有。这是一个有趣且好主意,但对我来说实施起来将是一项不平凡的工作。我的代码需要处理跨越相当大“宽度”范围的现有表,即一些表只包含几列,而另一些表包含 200 多列。我想我只是硬着头皮使用SqlBulkCopy 和自定义IDataReader 实现,这样我就可以 semi-bulk / chunk 从我的来源插入“流式传输”数据。 使用分析器查看哪些特定区域需要更长时间。 @MartinSmith 这是一个很好的建议。 Visual Studio 性能探查器似乎表明我的插入方法中的某些 LINQ 扩展方法明显较慢。更换扩展方法后,特定类型的代码在不到 2 分钟的时间内完成。它仍然慢了 30-40%,但我生成参数的代码似乎很可能是罪魁祸首。 “从 nvarchar 转换为每个相关的目标表列类型”是什么意思?你真的应该为 NVARCHAR 东西设置最大大小。此外,传递没有直接映射的数据类型也会影响性能:object/SQL_VARIANT、DATETIME(而不是 DATETIME2)、DECIMAL、Guid/UNIQUEIDENTIFIER 等。 【参考方案1】:

答案确实取决于您正在运行的数据库,但它与字符编码过程有关。 SQL Server 引入了 NVarChar 和 NText 字段类型来处理 UTF 编码的数据。 UTF 也恰好是 .NET CLR 的内部字符串表示形式。 NVarChar 和 NText 不必转换为另一种字符编码,这需要很短但可衡量的时间。

其他数据库允许您在数据库级别定义字符编码,而其他数据库允许您逐列定义它。性能差异实际上取决于驱动程序。

同样需要注意:

使用准备好的语句插入会强调转换为数据库内部格式的效率低下 这与数据库查询字符串的效率无关 - UTF-16 比用于 Text 和 VarChar 的默认 Windows-1252 编码占用更多空间。 当然,在全局应用程序中,UTF 支持是必要的

【讨论】:

我想我明白你在写什么,将数据插入varchar 列当然是有意义的。但是我首先注意到这种差异的特定表包含具有许多不同数据类型的列,例如decimal, int, bit` – 而且,现在我刚刚检查过,它的所有“文本”列都是 nvarchar 类型(而不是 nvarchar(MAX))。 所有事情都是平等的,只有 nvarchar 和 varchar 不同,你会看到编码的价格。我假设(因为您没有提供实际的性能数据)差异很小,但可以衡量。 我没有任何 varchar 类型 - 所以我的“特定类型”代码不应该进行任何编码。【参考方案2】:

它们不是(但它们几乎一样快)

我最初的差异完全是我的错。我为“退化”或“特定类型”版本的代码创建SqlParameter 对象的方式比代码的“nvarchar”版本使用了一个额外的循环。一旦我重写了特定于类型的代码以使用相同数量的循环(一个),性能几乎相同。 [现在慢了大约 1-2%,而不是慢了 500-800%。]

类型特定代码的略微修改版本现在更快一点;至少基于我的(有限)测试——大约 37,000 次命令执行速度提高了 3-4%。

但是它仍然(有点)令人惊讶的是它没有更快,因为我希望 SQL Server 将数百个 nvarchar 值转换为许多其他数据类型(对于每次执行)比 C# 代码向参数对象添加类型信息要慢得多。我猜很难观察到太大的差异,因为 SQL Server 转换参数值的时间相对于所有其他代码(包括与 SQL Server 通信的 SQL 客户端代码)的时间相当短。

我希望记住的一个教训是,将同类与同类进行比较非常很重要。

另一个看似的教训是 SQL Server 在将文本转换为各种其他数据类型方面非常快。

【讨论】:

以上是关于为啥“nvarchar”参数比“文本”“SqlCommand”命令的其他类型更快?的主要内容,如果未能解决你的问题,请参考以下文章

为啥我得到“程序需要'ntext/nchar/nvarchar'类型的参数'@statement'。”当我尝试使用 sp_executesql 时?

为啥链接区域的高度比文本大得多?

oracle中char的效率为啥比varchar的高一些了

为啥 IE 使密码框比文本框小?

如何一次性将所有 varchar 列/变量/参数转换为 nvarchar 类型?

为啥类型参数比方法参数强