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


236

我正在使用,在学校为小型Web应用程序开发数据库SQL Server 2005
我看到一些关于varcharvs 问题的流派nvarchar

  1. 使用varchar除非你处理了很多国际化的数据,然后使用nvarchar
  2. 只需使用nvarchar一切。

我开始看到视图2的优点。我知道nvarchar确实占用了两倍的空间,但这并不一定是什么大问题,因为这只会存储数百名学生的数据。在我看来,最简单的方法就是不用担心,只允许所有内容都使用nvarchar。还是我想念的东西?


类似的问题在这里:stackoverflow.com/questions/312170/…le dorfier编辑:有趣的是得出了完全相反的结论。
Booji Boy

6
参考了更广泛的线索,得出了相反的结论。stackoverflow.com/questions/312170/…–
dkretz

2
杰森(Jason):我希望这不是不适当的要求,但是您能否考虑将已接受的答案更改为gbn's。JoeBarone的答案有很多错误,这是非常错误的。让它被“接受”会误导新手做出错误的选择。“始终使用NVARCHAR” 是不必要且浪费的,并且会对性能和硬件成本/预算产生非常不利的影响。几行,甚至几千行都没有关系。但是系统的增长速度超出了人们的预期,因此当前公认的答案对社区不利。谢谢。
所罗门·鲁兹基

Answers:


140

始终使用nvarchar。

对于大多数应用程序,您可能永远不需要双字节字符。但是,如果需要支持双字节语言,并且数据库模式中仅支持单字节,那么回头修改整个应用程序确实很昂贵。

将一个应用程序从varchar迁移到nvarchar的成本将比您在大多数应用程序中使用的额外磁盘空间少得多。


4
返回并添加对多语言文本/消息,时区,度量单位和货币的支持要困难得多,因此每个人都必须始终从第一天开始就在应用程序中编写这些代码(即使它仅在您的主页上)应用)!
KM。

82
索引大小,内存使用情况等如何?我假设您总是会使用int,而您也可能太“以防万一”地使用tinyint吗?
gbn

99
始终为多语言站点进行编码/计划(当您不介意自己会用到它时)就像告诉所有年轻人,他们应该为自己的第一辆车购买大型8座,耗油量大的SUV ...毕竟,他们可能有一天会结婚,并且可能有6个孩子。我宁愿享受性能和效率,也可以在需要时支付升级费用。
EJ Brennan

4
@cbmeeks:我为我所不知道的编写代码。但是,如果您可以在没有明显性能下降的情况下使用它,那么您的数据库就不够大了……
gbn 2012年

60
通常,当人们以“始终”一词开始回答时,您应该忽略其后的所有内容。(注意,我以“通常”一词开始该声明:)
布兰登·摩尔

226

磁盘空间不是问题……但是内存和性能将成为问题。页面读取翻倍,索引大小翻倍,奇怪的LIKE和=恒定行为等

您需要存储中文等脚本吗?是还是不是...

并来自MS BOL“ Unicode的存储和性能影响

编辑

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

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


19
+1,如果您的应用程序走向国际,您将有很多其他问题要担心是否要对nvarchar进行搜索/替换:多语言文本/消息,时区,度量单位和货币
KM。

2
但是,如果您有时需要存储外来名称,例如José或Bjørn,该怎么办?
Qwertie 2012年

7
@Qwertie:然后您使用nvarchar。您不执行的操作会不必要地使用它。无论如何,这两个名称都适合varchar IIRC
gbn

6
说磁盘空间不是每个人都不是问题。我们已经在大型银行应用程序中不必要地天真地使用了nvarchar,因为该应用程序存储了数十亿年的记录。使用带有复制,备份和灾难恢复的昂贵的基于SAN的存储,这实际上可以转化为nvarchar vs varchar数百万美元的成本。更不用说对每次读取都要从磁盘读取两倍的字节,这会对性能造成很大的影响(100%)。
codemonkey 2014年

2
@codemonkey等:在下面的文章中,我竭尽全力解决了空间浪费的问题:磁盘便宜!奥利?(不过,需要免费注册)。本文旨在帮助防止Codemonkey遇到昂贵的企业级存储的情况。
所罗门·鲁茨基2015年

59

始终如一!将VARCHAR联接到NVARCHAR会对性能产生重大影响。


115
如果要对字符字段进行联接,那么通常来说,与使用nvarchar还是varchar相比,数据库可能存在更糟糕的问题。
布兰登·摩尔

@Thomas Harlan一个简单的测试向我证明,加入nvarcharvarchar转换nvarcharvarchar,再加入并没有明显的区别varchar。当然,除非您的意思是列数据类型一致,而不是联接一致。
ajeh

1
@ajeh和Thomas:1)“简单”测试常常会误导人们,因为它们没有涵盖引起行为差异的变化。2)如果在混合VARCHAR和时看到性能急剧下降NVARCHAR,那应该是由于该VARCHAR列的索引以及用于该列的排序规则的类型(以及索引)所致。我在以下博客文章中详细介绍了此主题:混合VARCHAR和NVARCHAR类型时对索引的影响
所罗门·鲁兹基

44

nvarchar的是要在内存,存储,工作组和索引显著的开销,因此,如果规范,决定了其真的会永远是必要的,不要打扰。

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

另一方面,在列的情况很多,我一定会提早问这个问题,如果我没有立即获得一个快速而准确的答案,我将列设为nvarchar。


26

我犹豫在这里添加另一个答案,因为已经有很多答案了,但是需要提出一些尚未提出或没有明确提出的观点。

第一:待办事项经常使用NVARCHAR。这是非常危险的,而且往往是昂贵的态度/方法。最好不要说“ 从不使用游标”,因为它们有时是解决特定问题的最有效方法,并且进行WHILE循环的常见解决方案几乎总是比正确完成游标要慢。

建议您“始终做最适合情况的”时,才应使用“始终”一词。当然,这通常很难确定,尤其是在试图平衡开发时间的短期收益时(经理:“我们需要此功能-您直到一周前才知道的功能!”)长期维护成本(最初向团队施加压力,要求其在3周的冲刺中完成3个月的项目的经理:“我们为什么会遇到这些性能问题?我们怎么可能做没有灵活性的X?我们负担不起一两个冲刺即可解决此问题。我们一周之内可以完成什么工作,以便我们可以重新处理优先事项?我们绝对需要在设计上花费更多的时间,以免这种情况不断发生!”)

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

  • 事务日志文件的大小
  • 复制所需的时间(如果使用复制)
  • ETL花费的时间(如果是ETLing)
  • 将日志传送到远程系统并还原所需的时间(如果使用日志传送)
  • 备份大小
  • 完成备份所需的时间
  • 进行恢复所花费的时间(这一天可能很重要;-)
  • tempdb所需的大小
  • 触发器的性能(用于存储在tempdb中的已插入和已删除表)
  • 行版本控制的性能(如果使用SNAPSHOT ISOLATION,则因为版本存储在tempdb中)
  • 当首席财务官说他们去年在SAN上花费了100万美元时,就可以获取新的磁盘空间,因此他们不会再授权25万美元用于额外的存储
  • 进行INSERT和UPDATE操作所需的时间
  • 维护索引所需的时间
  • 等等等等

浪费空间对整个系统具有巨大的级联效应。我写了一篇文章,详细介绍了该主题:磁盘便宜!奥利?(需要免费注册;抱歉,我无法控制该政策)。

第三:虽然有些答案错误地侧重于“这是一个小应用程序”方面,而有些答案正确地建议“使用适当的内容”,但这些答案都没有为OP提供真正的指导。问题中提到的重要细节这是他们学校的网页。大!因此,我们建议:

  • 对于学生和/或教师的名字字段应该大概NVARCHAR因为,随着时间的推移,它只是变得更可能是来自其他文化的名称将显示在那些地方了。
  • 但是街道地址和城市名称呢?该应用程序的用途并未说明(这将有所帮助),但假定地址记录(如果有)仅与特定地理区域(即,一种语言/文化)有关,然后VARCHAR与适当的代码页一起使用(由字段的排序规则确定)。
  • 如果存储州和/或国家/地区的ISO代码(无需存储INT/,TINYINT因为ISO代码是固定长度的,易于阅读,而且是标准的:),请使用CHAR(2)两个字母代码,CHAR(3)如果使用3个字母代码。并考虑使用二进制排序规则,例如Latin1_General_100_BIN2
  • 如果存储邮政编码(即邮政编码),请使用,VARCHAR因为这是国际标准,切勿使用AZ以外的任何字母。是的,VARCHAR即使仅存储美国邮政编码而不是INT ,也仍要使用,因为邮政编码不是数字,它们是字符串,并且其中一些带有前导“ 0”。并考虑使用二进制排序规则,例如Latin1_General_100_BIN2
  • 如果存储电子邮件地址和/或URL,请使用,NVARCHAR因为这两个现在都可以包含Unicode字符。
  • 等等....

第四:现在,您的NVARCHAR数据占用的空间是正常存储的数据VARCHAR(“正常存储” =不会变成“?”)所需空间的两倍,并且就像魔术一样,应用程序确实在增长现在至少在这些字段之一中有数百万条记录,其中大多数行是标准ASCII,但有些行包含Unicode字符,因此必须保留NVARCHAR,请考虑以下几点:

  1. 如果您使用的是SQL Server 2008-2016 RTM 在Enterprise Edition上,或者使用SQL Server 2016 SP1(使Data Compression在所有版本中均可用)或更高版本,则可以启用Data Compression。数据压缩可以(但不会“总是”)压缩NCHARand NVARCHAR字段中的Unicode数据。决定因素是:

    1. NCHAR(1 - 4000)NVARCHAR(1 - 4000)使用Unicode标准压缩方案,但只能在SQL Server 2008 R2中开始,并且仅对行数据有效,而不能用于过流!这似乎比常规的ROW / PAGE压缩算法要好。
    2. NVARCHAR(MAX)并且XML(而且我猜也是VARBINARY(MAX)TEXTNTEXT)行内(不在LOB或OVERFLOW页中的行外)数据至少可以进行PAGE压缩,而不能进行 ROW压缩。当然,PAGE压缩取决于行内值的大小:我使用VARCHAR(MAX)进行了测试,发现不会压缩6000个字符/字节的行,但是会压缩4000个字符/字节的行。
    3. 任何行外数据,LOB或OVERLOW =无需压缩!
  2. 如果使用SQL Server 2005或2008-2016 RTM 而不在Enterprise Edition上使用,则可以有两个字段:一个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]计算列中进行选择。对于插入和更新,您可以通过查看转换是否会改变输入值来确定要使用哪个字段,该字段必须是以下NVARCHAR类型:

    INSERT INTO TableName (..., URLa, URLu)
    VALUES (...,
            IIF (CONVERT(VARCHAR(2048), @URL) = @URL, @URL, NULL),
            IIF (CONVERT(VARCHAR(2048), @URL) <> @URL, NULL, @URL)
           );
  3. 您可以将传入值GZIP VARBINARY(MAX)压缩,然后在解压缩时解压缩:

    • 对于SQL Server 2005-2014:可以使用SQLCLR。SQL#(我编写的SQLCLR库)在免费版本中随附Util_GZipUtil_GUnzip
    • 对于SQL Server 2016及更高版本:您可以使用内置COMPRESSDECOMPRESS函数,它们也是GZip。
  4. 如果使用SQL Server 2017或更高版本,则可以考虑使该表成为集群列存储索引。

  5. 虽然这不是一个可行的选择,但SQL Server 2019在VARCHAR/ CHAR数据类型中引入了对UTF-8的本机支持。当前有太多的错误需要使用,但是,如果它们已修复,则在某些情况下是一种选择。请参阅我的文章《SQL Server 2019中的本机UTF-8支持:救星还是假先知?》,以详细了解此新功能。


7
拍手慢。只是惊讶地发现“总是使用nvarchar”获得了140票,但没有。在这篇文章上的出色工作。
schizoid04

1
@ schizoid04谢谢。公平地讲,被接受的答案是在我的7年之前发布的,因此有很多流量对此(和/或其他各种方式)进行了投票,但从未重新评估。仍然,它为推动基于投票的论坛的“人群的智慧”理论提供了非常坚实的对立面。那里有太多的错误信息。例如,对DBA.SE. 在我发布我的文章之前,另一个答案在最狭义的定义上是“正确的”,具有误导性,并且包含我在我的文章中所反对的信息,但仍然超过我的答案。
所罗门·鲁兹基

22

对于您的应用程序,nvarchar很好,因为数据库很小。说“总是使用nvarchar”是一个极大的简化。如果您不需要存储汉字或其他疯狂角色之类的东西,请使用VARCHAR,它将减少很多空间。我前任的前任在不需要时使用NVARCHAR设计了一些东西。我们最近将其切换为VARCHAR,仅在该表上保存了15 GB(已被高度写入)。此外,如果您在该表上有一个索引,并且想要包括该列或创建一个复合索引,则只需增加索引文件的大​​小即可。

请慎重考虑您的决定;在SQL开发和数据定义中,似乎很少有“默认答案”(当然,除了不惜一切代价避免使用游标)。


10

由于您的应用程序很小,因此与使用varchar相比,使用nvarchar基本上不会增加​​任何成本,并且如果需要存储unicode数据,则可以避免麻烦。


8

一般来说; 从约束最少的最昂贵的数据类型开始。投入生产。如果性能开始成为问题,请找出这些nvarchar列中实际存储的内容。那里有不适合的字符varchar吗?如果不是,请切换到varchar。在您知道痛苦在哪里之前,不要尝试进行预优化。我的猜测是,在可预见的将来,nvarchar / varchar之间的选择不会减慢您的应用程序的运行速度。在应用程序的其他部分,性能调整将为您带来更多收益


7

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

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

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

YMMV。


4
您正在使用的表中可能没有几亿条记录。我同意,对于大多数默认设置为nvarchar的应用程序来说,它可以,但不是全部。
布兰登·摩尔

7

我可以从经验上讲,谨防nvarchar。除非您绝对要求,否则此数据字段类型会破坏较大数据库的性能。我继承了一个在性能和空间方面都受到损害的数据库。我们能够将30GB数据库的大小减少70%!还进行了其他一些修改以帮助提高性能,但是我敢肯定,varchar的修改也可以大大改善性能。如果您的数据库有潜力将表增加到一百万条以上,则nvarchar不惜一切代价。


4

我经常在工作中处理这个问题:

  • 清单和价格的FTP提要-varchar正常工作时,项目描述和其他文本在nvarchar中。将它们转换为varchar可以将文件大小减少近一半,并且确实有助于上传。

  • 在有人在商品说明中添加特殊字符之前,上述方案可以正常工作(也许是商标,不记得了)

我还是不会每次都使用varchar来使用nvarchar。如果对特殊字符有任何疑问或潜力,请使用nvarchar。我发现当我完全控制填充字段的内容时,通常会使用varchar。


3

为什么在所有讨论中都没有提到UTF-8?能够存储完整的unicode字符并不意味着必须始终为每个字符分配两个字节(或使用UNICODE术语的“代码点”)。所有ASCII均为UTF-8。SQL Server是否检查VARCHAR()字段以确保文本是严格的ASCII(即最高字节位为零)?我希望不会。

如果要存储unicode 希望与旧的纯ASCII应用程序兼容,我认为使用VARCHAR()和UTF-8将是神奇的子弹:它仅在需要时使用更多空间。

对于那些不熟悉UTF-8的人,我建议使用底漆


2
您所建议的内容可能适用于某些应用程序,但还必须考虑额外的编码层对SQL文本处理方式的影响。特别地,将进行归类,搜索和模式匹配。而且,如果针对数据库运行报告,则标准报告工具将无法正确插入多字节字符。并且可能会影响大宗进出口。我认为,从长远来看,该方案可能比其价值更大。
Jeffrey L Whitledge,2009年

1
无法将UTF-8存储在VARCHAR列中。MSSQL将始终将您的UTF-8数据转换为列排序规则。如果您弄乱了排序规则(例如尝试将CP1252存储在Latin_1中),则转换将无法进行,并且最终将在数据中产生额外的字节。当您将latin_1转换为UTF-8(在应用程序侧)并再次返回到latin_1(数据库侧)时,它似乎工作正常,但这只是一种幻想。您可以通过使用freetds并将协议设置为小于7的数据库来自动将数据库自动转换为列排序规则,但是您将无法查询nvarchar。
chugadie 2013年

1
@chugadie和Tevya:这个答案有点荒谬。SQL Server仅使用UCS-2 / UTF-16来存储Unicode数据(即XML和N前缀类型)。您无法选择使用UTF-8。另外,Unicode编码(UTF-8,UCS-2 / UTF-16和UTF-32)不能应用于VARCHAR字段。
所罗门·鲁兹基2015年

2

在某些特殊情况下,您将有意限制数据类型以确保它包含特定集合中的字符。例如,我有一个需要将域名存储在数据库中的方案。域名的国际化当时并不可靠,因此最好在基础级别上限制输入,并帮助避免任何潜在的问题。


1

如果NVARCHAR仅由于系统存储过程需要它而使用它,最频繁发生的事件莫名其妙sp_executesql,并且您的动态SQL很长,那么从性能的角度来看,最好在执行所有字符串操作(连接,替换等)VARCHAR后再进行转换最终结果发送NVARCHAR给proc参数。所以不,不要总是使用NVARCHAR

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.