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

我正在为我的学校使用SQL Server 2005的一个小web应用程序的数据库工作 在varchar vs nvarchar的问题上,我看到了几个学派的思想:

  1. 使用varchar,除非你要处理大量国际化的数据,否则使用nvarchar
  2. 只要用nvarchar来处理所有事情。

我开始看到观点二的优点了。我知道nvarchar占用了两倍的空间,但这并不一定是一个大问题,因为它只存储几百个学生的数据。对我来说,不担心它,允许所有东西都使用nvarchar似乎是最简单的方法。还是我遗漏了什么?

181137 次浏览

总是使用nvarchar。

对于大多数应用程序,您可能永远都不需要双字节字符。但是,如果您需要支持双字节语言,而您的数据库模式中只有单字节支持,那么返回并在整个应用程序中进行修改是非常昂贵的。

将一个应用程序从varchar迁移到nvarchar的成本将远远超过在大多数应用程序中使用的一点点额外磁盘空间。

由于您的应用程序很小,使用nvarchar与使用varchar相比,基本上没有明显的成本增加,而且如果您需要存储unicode数据,您也省去了潜在的麻烦。

磁盘空间不是问题…但是记忆和性能会。 双倍的页面读取,双倍的索引大小,奇怪的LIKE和=恒定的行为等

你需要存储中文等脚本吗?是或不是…

And from MS BOL "Unicode的存储和性能影响"

编辑:

最近的SO问题强调了nvarchar性能有多差…

SQL Server在搜索nvarchar字符串时使用高CPU

是一致的!加入一个VARCHAR到NVARCHAR有一个很大的性能打击。

nvarchar将在内存、存储、工作集和索引方面有很大的开销,所以如果规范规定确实需要从来没有,就不要费心了。

我不会有一个硬性的“总是nvarchar”规则,因为它在许多情况下完全是浪费——特别是来自ASCII/EBCDIC的ETL或标识符和代码列,它们通常是键和外键。

另一方面,有很多列的情况,在这些情况下,我肯定会在早期提出这个问题,如果我没有立即得到一个明确而快速的答案,我将使列为nvarchar。

我在工作中经常遇到这样的问题:

  • 库存和定价的FTP提要-当varchar工作正常时,项目描述和其他文本是在nvarchar中。将这些文件转换为varchar可以将文件大小减少近一半,并且对上传非常有帮助。

  • 上面的场景工作得很好,直到有人在商品描述中添加了一个特殊字符(可能是商标,不记得了)

我还是不会每次都用varchar。如果有任何疑问或特殊字符的潜力,我使用nvarchar。我发现,当我100%控制填充字段的内容时,我主要使用varchar。

对于您的应用程序,使用nvarchar很好,因为数据库大小较小。说“总是使用nvarchar”是一种极大的过度简化。如果你不需要存储像汉字或其他疯狂的字符,使用VARCHAR,它会占用更少的空间。我目前工作的前任在不需要的时候使用NVARCHAR设计了一些东西。我们最近将它切换到VARCHAR,仅在这个表上就节省了15gb(它被高度写入)。此外,如果您在该表上有一个索引,并且希望包含该列或创建一个复合索引,那么您只是使索引文件的大小变大了。

做决定时要考虑周全;在SQL开发和数据定义中,似乎很少有“默认答案”(当然,除了不惜一切代价避免游标)。

在过去的几年里,我们所有的项目都使用了NVARCHAR,因为所有这些项目都是多语言的。从外部源导入的数据(例如ASCII文件等)在插入到数据库之前被上转换为Unicode。

我还没有遇到任何与较大索引相关的性能问题,等等。索引确实会使用更多的内存,但是内存很便宜。

无论您是使用存储过程还是动态构造SQL,都要确保所有字符串常量都有N前缀(例如SET @foo = N' hello world.';),这样常量也是Unicode。这避免了在运行时进行任何字符串类型转换。

YMMV。

为什么在所有这些讨论中,没有提到UTF-8?能够存储完整的unicode字符跨度并不意味着必须总是为每个字符分配两个字节(或使用unicode术语的“码位”)。所有的ASCII都是UTF-8。SQL Server检查VARCHAR()字段,文本是严格的ASCII(即顶部字节位零)?我希望不是。

如果你想存储unicode 而且,想要与旧的只使用ascii的应用程序兼容,我认为使用VARCHAR()和UTF-8将是神奇的子弹:它只在需要时使用更多的空间。

对于那些不熟悉UTF-8的人,我推荐一个引物

我可以从经验上讲,小心nvarchar。除非绝对需要,否则这种数据字段类型会破坏大型数据库的性能。我继承了一个在性能和空间方面受到损害的数据库。我们能够将30GB的数据库大小减少70%!有一些其他的修改,以帮助性能,但我相信varchar的帮助显著以及。如果你的数据库有可能将表增加到100万多条记录,那么无论如何都要远离nvarchar

一般来说;从具有最少约束的最昂贵的数据类型开始。投入生产。如果性能开始出现问题,找出这些nvarchar列中实际存储的内容。这里是否有不适合varchar的字符?如果不是,切换到varchar。在你知道问题在哪里之前,不要尝试预先优化。我的猜测是nvarchar/varchar之间的选择不会降低应用程序的速度在可预见的未来。在应用程序的其他部分,性能调优会给你更多物有所值

会有一些例外的情况,当你想要故意限制数据类型,以确保它包含特定集合的字符时。例如,我有一个场景,我需要在数据库中存储域名。域名的国际化在当时是不可靠的,所以最好限制在基础水平上的输入,并有助于避免任何潜在的问题。

我不愿意在这里再补充一个答案,因为已经有很多了,但有几个问题需要说明,这些问题要么没有说明,要么没有讲清楚。

总是使用NVARCHAR。这是一种非常危险,而且通常代价高昂的态度/方法。而且说“从来没有 use cursors"也好不到哪里去。因为它们有时是解决特定问题的最有效的方法,并且执行WHILE循环的常见变通方法几乎总是比正确完成的游标慢。

你唯一应该使用“总是”这个词的时候;就是建议“总是做对情况最有利的事情”。当然,这通常很难确定,特别是在试图平衡开发时间的短期收益时(经理:“我们需要这个功能——你直到现在才知道——一周前!”)和长期维护成本(经理最初迫使团队在3周的冲刺中完成一个3个月的项目:“为什么我们会有这些性能问题?”我们怎么可能做没有灵活性的X呢?我们无法承受一两次冲刺来解决这个问题。我们可以在一周内完成哪些工作,以便回到优先事项上?我们确实需要在设计上花更多的时间,这样才不会一直发生这样的事情!”)。

第二: @gbn的回答涉及到一些非常重要的点,当路径不是100%清晰时,在做出某些数据建模决策时需要考虑。但还有更多需要考虑的问题:

  • 事务日志文件的大小
  • 复制所花费的时间(如果使用复制)
  • ETL所需的时间(如果ETLing)
  • 将日志发送到远程系统并恢复所需的时间(如果使用日志发送)
  • 备份的大小
  • 完成备份所需的时间
  • 执行恢复所需的时间长度(这在将来可能很重要;-)
  • tempdb所需的大小
  • 触发器的性能(用于插入和删除存储在tempdb中的表)
  • 行版本控制的性能(如果使用SNAPSHOT ISOLATION,因为版本存储在tempdb中)
  • 当首席财务官说他们去年刚刚花了100万美元买了一台SAN,所以他们不会批准另外25万美元用于额外的存储空间时,他们无法获得新的磁盘空间
  • 执行INSERT和UPDATE操作所需的时间长度
  • 进行索引维护所需的时间长度
  • 等等,等等。

浪费空间对整个系统有巨大的级联效应。我写了一篇文章,详细介绍了这个主题:磁盘很便宜!奥利吗?(需要免费注册;对不起,我不掌握这项政策)。

第三:虽然有些答案不正确地集中在“这是一个小应用程序”;问题中提到的一个重要细节是,这是他们学校的网页,有些人正确地建议“使用适当的”,没有一个答案给O.P.提供了真正的指导。太棒了!所以我们可以提出:

  • 学生和/或教师名字的字段应该可能NVARCHAR,因为随着时间的推移,来自其他文化的名字只会越来越有可能出现在这些地方。
  • 但是街道地址和城市名称呢?该应用程序的目的没有说明(这将是有帮助的),但假设地址记录(如果有的话)只属于特定的地理区域(即单一语言/文化),然后使用VARCHAR与适当的代码页(这是由字段的Collation决定的)。
  • 如果存储州和/或国家的ISO代码(不需要存储INT / TINYINT,因为ISO代码是固定长度的,人类可读,并且是标准:),则使用CHAR(2)作为两个字母代码,使用CHAR(3)作为三个字母代码。并考虑使用二进制排序规则,如Latin1_General_100_BIN2
  • 如果存储邮政编码(即邮政编码),使用VARCHAR,因为它是一个国际标准,永远不要使用A-Z以外的任何字母。是的,仍然使用VARCHAR,即使只存储美国邮政编码,而不是INT,因为邮政编码不是数字,它们是字符串,其中一些有一个前导"0"。并考虑使用二进制排序规则,如Latin1_General_100_BIN2
  • 如果存储电子邮件地址和/或url,请使用NVARCHAR,因为它们现在都可以包含Unicode字符。
  • 等等....

第四:现在你有NVARCHAR数据占用的空间是适合VARCHAR的数据所需空间的两倍("非常适合"=不会变成"?"),不知怎么的,就像变魔术一样,应用程序确实增长了,现在在这些字段中至少有一个字段中有数百万条记录,其中大多数行是标准ASCII,但有些包含Unicode字符,所以你必须保留NVARCHAR,考虑以下情况:

  1. 如果您正在使用SQL Server 2008 - 2016 RTM 而且在企业版,或者如果使用SQL Server 2016 SP1(使数据压缩在所有版本中可用)或更新版本,那么您可以启用数据压缩。数据压缩可以(但不会“总是”)压缩NCHARNVARCHAR字段中的Unicode数据。决定因素有:

  2. NCHAR(1 - 4000)NVARCHAR(1 - 4000)使用Unicode标准压缩方案,但只能从SQL Server 2008 R2开始,并且仅用于in ROW数据,而不是OVERFLOW!这似乎比常规的ROW / PAGE压缩算法更好。

  3. NVARCHAR(MAX)XML(我猜也是VARBINARY(MAX)TEXT,和NTEXT)数据在ROW(不是LOB或OVERFLOW页中的off行)至少可以被PAGE压缩,但 ROW压缩。当然,PAGE压缩取决于行内值的大小:我用VARCHAR(MAX)进行了测试,发现6000个字符/字节的行不能压缩,但4000个字符/字节的行可以压缩。

  4. 任何OFF ROW数据,LOB或OVERLOW =没有压缩!

  5. 如果使用SQL Server 2005,或2008 - 2016 RTM和企业版上的,你可以有两个字段:一个VARCHAR和一个NVARCHAR。例如,假设你存储的url大部分都是基本ASCII字符(值0 - 127),因此适合VARCHAR,但有时也有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)
    );
    
  6. 你可以将传入的值GZIP到VARBINARY(MAX)中,然后在输出时解压缩:

    • 对于SQL Server 2005 - 2014:可以使用SQLCLR。SQL #(我编写的SQLCLR库)在免费版本中附带Util_GZipUtil_GUnzip
    • 对于SQL Server 2016及更新版本:您可以使用内置的COMPRESSDECOMPRESS函数,它们也是GZip。
  7. 如果使用SQL Server 2017或更新版本,可以考虑将表设置为集群Columnstore索引。

  8. 虽然这还不是一个可行的选项,但SQL Server 2019在VARCHAR / CHAR数据类型中引入了对UTF-8的本机支持。目前它有太多的bug,不能使用,但如果它们被修复了,那么这是一些场景的一个选项。关于这个新功能的详细分析,请参阅我的帖子“__abc3”。

如果你使用NVARCHAR只是因为系统存储过程需要它,最常见的情况是莫名其妙的sp_executesql,而且你的动态SQL非常长,从性能角度来看,你最好在VARCHAR中进行所有字符串操作(连接、替换等),然后将最终结果转换为NVARCHAR并将其输入到proc参数中。所以,不要总是使用NVARCHAR!