SQL Server中的varchar和nvarchar数据类型的主要性能差异是什么?

248

我正在使用 SQL Server 2005 开发学校的小型Web应用程序的数据库。
在使用 varcharnvarchar 的问题上,我看到了几种不同的观点:

  1. 如果你没有处理大量国际化数据,那就使用 varchar,否则使用 nvarchar
  2. 对于所有内容都使用 nvarchar

我开始看到第二个观点的优点。我知道 nvarchar 占用的空间是 varchar 的两倍,但考虑到这只会为几百名学生存储数据,这并不是一个很大的问题。对我来说,似乎最容易的方法是不用担心它,让所有内容都使用 nvarchar。还是有什么我没注意到的吗?


类似的问题在这里:https://dev59.com/xXVC5IYBdhLWcg3wYQAp。有趣的是,它得出了完全相反的结论。 - Booji Boy
6
参考更广泛的讨论串,该串得出了相反的结论。https://dev59.com/xXVC5IYBdhLWcg3wYQAp - dkretz
3
Jason:我希望这不是一个不恰当的要求,但是你能否考虑将已接受的答案更改为 gbn's。JoeBarone的答案有许多严重错误,如果它被“接受”,会误导新手做出错误的选择。无需“总是使用NVARCHAR”,这样做可能对性能和硬件成本/预算产生非常负面的影响。少数行,甚至几千行都没有关系,但系统增长速度比人们预期的更快,所以目前接受的答案是对社区的不利影响。谢谢。 - Solomon Rutzky
14个回答

236

20
如果你的应用程序国际化,你将会面临许多其他问题,比如多语言文本/消息、时区、计量单位和货币。+1 - KM.
2
但是如果您有时需要存储外国名称,例如José或Bjørn呢? - Qwertie
7
@Qwertie:那么您可以使用nvarchar。不要不必要地使用它。如果我没记错,这两个名称都适合于varchar。 - gbn
14
说磁盘空间不是一个问题并不适用于每个人。在一个存储了数十亿条记录多年的大型银行应用程序中,我们过于天真地不必要地使用了nvarchar。使用昂贵的基于SAN的存储解决方案进行复制、备份和灾难恢复时,与varchar相比,这实际上可能会转化为数百万美元的成本。更不用说读取两倍字节的性能影响非常大(100%)。 - codemonkey
3
@codemonkey等:我在以下文章中尽力从整体上解决了浪费空间的问题:Disk Is Cheap! ORLY?(需要免费注册)。该文章旨在帮助避免像codemonkey遇到的昂贵的企业级存储情况。 - Solomon Rutzky
显示剩余4条评论

132

始终使用nvarchar。

大多数应用程序可能永远不需要双字节字符。 但是,如果您需要支持双字节语言,并且数据库模式仅支持单字节,则返回并在整个应用程序中进行修改的成本真的很高昂。

将一个应用程序从varchar迁移到nvarchar的成本要比在大多数应用程序中所需的一点额外磁盘空间要高得多。


87
索引大小、内存使用等方面怎么样?我猜你总是使用 int,即使你也可以使用 tinyint,只是为了以防万一? - gbn
108
在你完全不知道自己将来是否需要的情况下,一直为多语言网站编写/规划代码,就像告诉所有年轻人他们应该买一个8座位、油耗大的越野车作为第一辆车一样...毕竟,他们未来可能会结婚,并有六个孩子。我宁愿在需要时付出升级的代价而在能使用的时候享受性能和效率。 - E.J. Brennan
77
通常当人们以“总是”这个词开始他们的回答时,你应该忽略后面说的一切。(注意我用“通常”这个词开始了这个陈述 :)) - Brandon Moore
7
这是极其糟糕的建议。“始终”使用NVARCHAR?你不是在为EMC或Oracle工作,对吧?;-) - Solomon Rutzky
13
我知道这已经过时了,但这是一个糟糕的建议,不应该被采纳为正确答案。 - shuniar
显示剩余8条评论

63

保持一致性!将VARCHAR和NVARCHAR连接在一起会极大地影响性能。


124
如果你在字符字段上执行连接操作,那么通常来说,你的数据库问题可能比选择使用nvarchar或varchar类型更加严重。 - Brandon Moore
@Thomas Harlan 一个简单的测试告诉我,在将 nvarchar 连接到 varchar 和将 nvarchar 转换为 varchar 并连接到 varchar 之间没有实质性的区别。除非您指的是在列数据类型上保持一致,而不是在连接上保持一致。 - ajeh
2
@ajeh和Thomas:1)“简单”的测试通常会误导人,因为它们没有涵盖导致行为差异的变化。2)如果在混合使用VARCHARNVARCHAR时看到了明显的性能下降,那应该是由于对VARCHAR列进行索引以及用于该列(因此索引)的排序规则的类型。我在以下博客文章中详细介绍了这个主题:混合使用VARCHAR和NVARCHAR类型时对索引的影响 - Solomon Rutzky
连接数据点:我们有3个视图,每个视图都有不到10k行数据,这些连接操作表现良好,但是添加一个22k行数据的视图后,查询时间需要2分钟,而10k行数据的视图只需要不到5秒。该领域为金融(股票),如果将股票符号更改为代理键,则会产生更大的影响。 - yzorg
当类型强制转换被推到所有行时,似乎存在一个“临界点”,查询时间会急剧下降。 - yzorg

49

nvarchar会在内存、存储、工作集和索引方面产生极大的开销,所以如果规格确定它确实永远不必要,那就不要浪费精力。

我不会有一个坚定的“总是使用nvarchar”的规则,因为在许多情况下,它可能是完全浪费资源的 - 特别是从ASCII/EBCDIC或标识符和代码列进行ETL时,这些通常是主键和外键。

另一方面,有许多列的情况下,我会确保在早期提出该问题,如果没有立即得到一个明确的答案,我会将该列设置为nvarchar。


48

虽然已经有不少答案了,但我还是要说几点,因为有些问题并没有得到解决,或者没有清楚地表达。

首先:不要总是使用NVARCHAR。这是一种非常危险、而且经常很昂贵的态度和方法。同样也不应该说“从不使用游标”,因为它们有时是解决特定问题最有效的手段,而且常见的用WHILE 循环代替游标的方法通常比正确地实现一个游标更慢。

你唯一能使用“始终”这个词的时候是建议“始终做最适合情况的事情”。当你试图在短期开发时间(经理:“我们需要这个功能——你直到刚才才知道——一周前!”)与长期维护成本(最初迫使团队在3周之内完成3个月项目的经理:“为什么我们会遇到这些性能问题?我们怎么可能做出没有灵活性的X?我们不能承受几周的停顿去修复这个问题。我们本周能做些什么来回到我们的优先事项?我们肯定需要花更多时间进行设计,以免这种情况持续发生!”)之间取得平衡时,这可能很难确定。

其次:@gbn的答案涉及到在某些数据建模决策上考虑的一些重要问题。但还有更多需要考虑的因素:

  • 事务日志文件的大小
  • 复制所需的时间(如果使用复制)
  • ETL所需的时间(如果正在进行ETL)
  • 将日志发送到远程系统并恢复所需的时间(如果使用日志传送)
  • 备份的大小
  • 完成备份所需的时间
  • 执行还原所需的时间(这可能在某天很重要)
  • 临时数据库所需的大小
  • 触发器的性能(对于存储在tempdb中的插入和删除表)
  • 行版本的性能(如果使用SNAPSHOT隔离,因为版本存储在tempdb中)
  • 当首席财务官说他们去年刚花了100万美元购买SAN,所以不会授权另外250k用于增加存储空间时,获得新磁盘空间的能力
  • 执行INSERT和UPDATE操作所需的时间
  • 执行索引维护所需的时间
  • 等等,等等。
浪费空间对整个系统有巨大的连锁影响。我写了一篇详细介绍这个问题的文章:Disk Is Cheap! ORLY?(需要免费注册;抱歉我无法控制该政策)。

第三点:尽管一些答案错误地关注了“这是一个小应用程序”的方面,而一些答案正确地建议“使用适当的内容”,但是没有一个答案为O.P.提供了真正的指导。在问题中提到了一个重要细节,即这是他们学校的网页。很好!因此我们可以建议:

  • 学生和/或教职员工姓名字段可能应该是如果你正在使用SQL Server 2008 - 2016 RTM的企业版,或者在使用SQL Server 2016 SP1(该版本在所有版本中提供了数据压缩功能)或更高版本,则可以启用数据压缩。数据压缩可以(但不总是)压缩NCHARNVARCHAR字段中的Unicode数据。影响因素如下:

  • NCHAR(1 - 4000)NVARCHAR(1 - 4000)使用标准的Unicode压缩方案,但仅支持从SQL Server 2008 R2开始,并且仅适用于行内数据,而不是溢出数据!这似乎比常规的ROW / PAGE压缩算法更好。

  • NVARCHAR(MAX)XML(我猜也包括VARBINARY(MAX)TEXTNTEXT)的行内数据可以至少进行PAGE压缩,但不能进行ROW压缩。当然,PAGE压缩取决于行内值的大小:我测试了VARCHAR(MAX),发现6000个字符/字节的行无法压缩,但4000个字符/字节的行可以。

  • 任何溢出数据,LOB或OVERLOW = 没有压缩!

  • 如果使用SQL Server 2005或2008 - 2016 RTM且不使用企业版,则可以有两个字段:一个VARCHAR和一个NVARCHAR。例如,假设您正在存储大多数都是基本ASCII字符(值为0 - 127)的URL,并且有时具有Unicode字符。您的模式可以包括以下3个字段:

  •    ...
       URLa VARCHAR(2048) NULL,
       URLu NVARCHAR(2048) NULL,
       URL AS (ISNULL(CONVERT(NVARCHAR([URLa])), [URLu])),
       CONSTRAINT [CK_TableName_OneUrlMax] CHECK (
                         ([URLa] IS NOT NULL OR [URLu] IS NOT NULL)
                     AND ([URLa] IS NULL OR [URLu] IS NULL))
     );
    

    在这个模型中,你仅从计算列[URL]中进行SELECT。对于插入和更新操作,需要确定使用哪个字段,方法是看转换是否会更改传入的值,而这个值必须是NVARCHAR类型:

     INSERT INTO TableName (..., URLa, URLu)
     VALUES (...,
             IIF (CONVERT(VARCHAR(2048), @URL) = @URL, @URL, NULL),
             IIF (CONVERT(VARCHAR(2048), @URL) <> @URL, NULL, @URL)
            );
    
    你可以将传入的值压缩成VARBINARY(MAX),然后在输出时解压缩:
    • 对于 SQL Server 2005 - 2014: 可以使用 SQLCLR。免费版本的SQL#(我编写的 SQLCLR 库)带有 Util_GZipUtil_GUnzip
    • 对于 SQL Server 2016 及更新版本:可以使用内置的 COMPRESSDECOMPRESS 函数,它们也是 GZip。
    如果使用的是 SQL Server 2017 或更新版本,则可以考虑将表制作为聚集列存储索引。 尽管这还不是一个可行的选择,但 SQL Server 2019 引入了对 VARCHAR / CHAR 数据类型中 UTF-8 的本机支持。目前存在太多缺陷使其无法使用,但如果修复了这些缺陷,则这是一些情况下的选项。请参阅我的帖子“SQL Server 2019 中的本机 UTF-8 支持:救世主还是伪先知?”,详细分析了这个新功能。

14
慢慢鼓掌。仅仅惊讶于“始终使用nvarchar”获得了140票而这个没有。对这篇文章的出色工作表示赞扬。 - schizoid04
1
@schizoid04 谢谢。公平地说,被接受的答案是在我之前发布了7年,所以有很多人投票支持它(或其他答案),但从未回来重新评估。尽管如此,它提供了一个非常坚实的反驳“群众智慧”理论的观点,这种理论推动了基于投票的论坛。有太多的错误信息存在。例如,在DBA.SE上的这个。在我发布我的答案之前被接受的另一个答案,按最狭义的定义是“正确”的,但具有误导性,并包含我在我的答案中证明为错误的信息,但它仍然超过了我的答案。 - Solomon Rutzky
1
除了一件事,这是一篇非常好的文章。我非常怀疑他们会很快允许使用中文、阿拉伯语或梵文文本中的名称。很多人认为重音符号可以使某些内容成为Unicode编码,但实际上并不是这样。 - PRMan

23

针对您的应用程序,使用nvarchar是可以的,因为数据库大小很小。但是说“总是使用nvarchar”是极其简化的。如果不需要存储像汉字或其他特殊字符等东西,可以使用VARCHAR,它将占用更少的空间。我现在的前任在设计时没有必要使用NVARCHAR。我们最近将其切换到VARCHAR,并仅在该表上就节省了15 GB(它被频繁写入)。此外,如果您在该表上创建索引并且想要包括该列或创建复合索引,则会使索引文件大小变大。

只需在决策时慎重考虑;在SQL开发和数据定义中似乎很少有“默认答案”(当然要尽量避免使用游标)。


11

由于您的应用程序规模较小,使用nvarchar而不是varchar实际上不会产生明显的成本增加,并且如果需要存储Unicode数据,这样可以避免未来可能出现的麻烦。


8

一般而言,首先选用最昂贵且限制最少的数据类型。将其投入生产环境。如果性能成为问题,查找实际存储在 nvarchar 列中的内容。其中是否包含不适用于 varchar 的字符?如果没有,则转换为 varchar。在确定痛点之前不要尝试预优化。我猜测,在可预见的未来,nvarchar/varchar 选择不会拖慢您的应用程序。在应用程序的其他部分进行性能调整会带来更多的效益。


哎呀,从应用程序开发者的角度来看,我真的不喜欢这种方法。如果代码是针对A类型编写的,而你将其更改为B类型,那么你必须重新进行全面的测试。我的建议是尽力识别预期数据,然后再进行操作。 - mateoc15

7

我可以从经验中说出来,要小心使用 nvarchar。除非您绝对需要它,否则这种数据字段类型会破坏大型数据库的性能。我继承了一个在性能和空间方面受到影响的数据库。我们能够将一个30GB的数据库大小减少70%!虽然还有一些其他的修改帮助了性能,但我相信 varchar 也对此起了显著的作用。如果您的数据库有可能增长到一百万条或更多记录,请千万不要使用 nvarchar


7
在过去的几年里,我们所有的项目都使用NVARCHAR,因为所有这些项目都是多语言的。从外部来源导入的数据(例如ASCII文件等)在插入到数据库之前会被升级为Unicode。
我还没有遇到任何与较大索引相关的性能问题等。索引确实使用更多的内存,但内存很便宜。
无论您使用存储过程还是构建SQL,请确保所有字符串常量都以N为前缀(例如SET @foo = N'Hello world.';),以使该常量也是Unicode。这可以避免运行时的任何字符串类型转换。
YMMV。

4
您可能在操作的表中没有几亿条记录。我同意对于大多数应用程序来说,默认使用nvarchar是可以的,但并非全部情况。 - Brandon Moore

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接