始终使用nvarchar(MAX)有什么缺点吗?


342

在SQL Server 2005中,使所有字符字段都为nvarchar(MAX)而不是显式指定长度(例如nvarchar(255))是否有任何缺点?(除了显而易见的那一句,您无法在数据库级别限制字段长度)


1
我不明白为什么要让别人输入8000多个字符的名称。
DForck42,2009年

2
相同的逻辑可以应用于编程语言。为什么不回到我们所有数据的旧VB6版本呢?我认为在一个以上的地方制衡并不一定是坏事。
马特·斯普拉德利


10
您的更新应该是该问题的答案。
2015年

由于原始作者尚未完成,因此将问题答案移至正确答案。stackoverflow.com/a/35177895/10245我认为7年的时间足够了:-)
蒂姆·阿贝尔

Answers:


153

在MSDN论坛上提出了相同的问题:

从原始帖子(那里有更多信息):

当您将数据存储到VARCHAR(N)列时,这些值实际上以相同的方式存储。但是,当将其存储到VARCHAR(MAX)列时,在屏幕后面会将数据作为TEXT值处理。因此,在处理VARCHAR(MAX)值时,需要一些其他处理。(仅当大小超过8000时)

VARCHAR(MAX)或NVARCHAR(MAX)被视为“大值类型”。大值类型通常存储在“行外”。这意味着数据行将具有指向另一个存储“大值”的位置的指针。


2
所以问题应该是,使用N / VARCHAR(MAX)和N / TEXT有区别吗?
Unsliced

18
如果我没记错的话,难道它们不是仅在大小超过8k时才存储在行外吗?
山姆·舒特

79
我将答案读为“不,使用N/VARCHAR(MAX)” 没有缺点,因为“只有在大小超过8000的情况下”才会进行其他处理。因此,仅在必要时才产生成本,并且数据库的限制较少。我读错了吗?似乎您几乎总是想要N/VARCHAR(MAX)而不是N/VARCHAR(1-8000)……
Kent Boogaart 2010年

1
上方的无效
Jagd

68
不幸的是,这个答案有很多问题。它使8k边界看起来像一个魔术数字,不是真的,该值基于可能更多的因素而被推出行,包括sp_tableoptionsmsdn.microsoft.com/zh-cn/library/ms173530.aspx。VARCHAR(255)类型也可以从行中移出,提到的“开销”对于MAX和255可能是完全相同的。当MAX类型与TEXT类型变得不同时,它将MAX类型与TEXT类型进行比较(完全不同的API进行操作,不同的存储空间等)。它没有提到实际的差异:没有索引,没有对MAX类型的在线操作
Remus Rusanu

50

这是一个公平的问题,除了明显的问题,他确实做了陈述。

缺点可能包括:

对性能的影响查询优化器使用字段大小来确定最有效的执行计划

“ 1.在扩展名和数据库页面中的空间分配是灵活的。因此,当使用更新向该字段添加信息时,如果新数据的长度大于先前插入的数据的长度,数据库将必须创建一个指针。数据库文件将变得零散=几乎所有性能(从索引到删除,更新和插入)的性能降低。“ http://sqlblogcasts.com/blogs/simons/archive/2006/02/28/Why-use-anything-but-varchar_2800_max_2900_.aspx

集成的含义-其他系统很难知道如何与数据库集成数据的意外增长可能的安全问题,例如,您可能由于占用所有磁盘空间而使系统崩溃

这里有一篇很好的文章:http : //searchsqlserver.techtarget.com/tip/1,289483,sid87_gci1098157,00.html


4
+1对于集成和安全性的影响。这些是大多数其他答案都谈到性能时要考虑的原始角度。与集成相关的问题是,如果所有列都为,则使用元数据来提供合理的默认控件大小的任何工具(例如报表编写者或表单设计者)都将需要进行大量工作varchar(max)
幻灭了

据我所知,通过数据库进行集成是最荒谬的事情。如果只是一次导入,则可以先通过LEN函数检查数据。
Maxim

30

根据接受的答案中提供的链接,看来:

  1. 一个nvarchar(MAX)字段中存储的100个字符将与一个字段中的100个字符存储相同nvarchar(100)-数据将以内联方式存储,并且您将没有“行外”读取和写入数据的开销。所以在那里没有后顾之忧。

  2. 如果大小大于4000,则数据将自动“按行存储”,这正是您想要的。因此,那里也不用担心。

然而...

  1. 您不能在nvarchar(MAX)列上创建索引。您可以使用全文索引,但不能在列上创建索引以提高查询性能。对我来说,这很划算……始终使用nvarchar(MAX)是绝对不利的。

结论:

如果您希望在整个数据库中使用一种“通用字符串长度”,可以对其进行索引并且不会浪费空间和访问时间,则可以使用nvarchar(4000)


1
仅供参考,这是对原始问题的补充,应将其发布为答案
Tim Abell,2016年

1
谢谢,对我来说,这是最终答案。我问自己一样- 为什么不一直使用nvarchar(max)-就像string在C#中一样?-但第3点)(索引问题)给出了答案。
SQL警察

1
添加了修改。作为一种“通用字符串长度”,您可以随时使用nvarchar(4000)
SQL Police

@SQLGeorge请参见Martin Smith的出色答案,它说明了声明比以往任何时候都更宽的列对查询性能的影响
billinkc

@billinkc谢谢,那是一篇很棒的文章。好的,因此大小确实会影响性能。我将再次编辑答案。
SQL警察

28

有时您希望数据类型对其中的数据施加某种意义。

举例来说,您有一列的长度不应超过20个字符。如果您将该列定义为VARCHAR(MAX),则某些流氓应用程序可能会在其中插入一个长字符串,而您永远不会知道,或者有任何防止它的方法。

下次您的应用程序使用该字符串时,假设该字符串的长度对于它表示的域而言是适中且合理的,则您将遇到无法预料和令人困惑的结果。


9
我同意这一点以及其他一些评论,但是我仍然坚持这是业务层的责任。到达数据库层时,无论长度多么荒谬,它都应该向致敬并存储值。我认为这里真正发挥作用的是,我认为大约90%的开发人员指定varchar(255)时,他的意图并不是真正的255个字符,而是一些未指定的中间长度值。考虑到我数据库中不合理的大值与无法预料的异常之间的平衡,我将采用大值。
克里斯·贝伦斯

4
如果他们指定VARCHAR(255)来指示一些未知的长度,那是他们没有正确研究他们正在设计的内容的错误。解决方案是让开发人员完成工作,而不是让数据库允许不合理的值。
Tom H

10
对作者没有帮助。他明确排除了您回答的问题。
usr

6
// @克里斯·B·贝伦斯:我不同意;数据库模式业务逻辑的一部分。表,关系,字段和数据类型的选择都是业务逻辑-使用RDBMS强制执行此业务逻辑的规则是值得的。由于一个原因,很少有一个应用程序层访问数据库。例如,您可能具有绕过主要业务层的数据导入和提取工具,这意味着您确实需要数据库来执行规则。
文斯·鲍德伦

1
如果您不需要或确实不希望存储长字符串,则最好在数据上增强意义。例如,如果存储一个PostCode字段,您会允许某人输入成百上千个字符,但最多不能超过10个。-应该验证所有级别,客户端,业务层和数据库的最大大小。如果使用诸如C#和Entity Framework之类的“模型优先”方法,则可以在模型上定义maxsize,并将其应用于数据库,业务逻辑和客户端验证(例如jquery验证)。仅在真正需要时使用nvarchar(max)
Peter Kerr

21

我检查了一些文章,并从中找到了有用的测试脚本:http : //www.sqlservercentral.com/Forums/Topic1480639-1292-1.aspx 然后将其更改为在NVARCHAR(10)与NVARCHAR(4000)与NVARCHAR(MAX)之间进行比较),使用指定数字但使用MAX时找不到速度差异。您可以自己进行测试。希望对您有所帮助。

SET NOCOUNT ON;

--===== Test Variable Assignment 1,000,000 times using NVARCHAR(10)
DECLARE @SomeString NVARCHAR(10),
        @StartTime DATETIME;
--=====         
 SELECT @startTime = GETDATE();
 SELECT TOP 1000000
        @SomeString = 'ABC'
   FROM master.sys.all_columns ac1,
        master.sys.all_columns ac2;
 SELECT testTime='10', Duration = DATEDIFF(ms,@StartTime,GETDATE());
GO
--===== Test Variable Assignment 1,000,000 times using NVARCHAR(4000)
DECLARE @SomeString NVARCHAR(4000),
        @StartTime DATETIME;
 SELECT @startTime = GETDATE();
 SELECT TOP 1000000
        @SomeString = 'ABC'
   FROM master.sys.all_columns ac1,
        master.sys.all_columns ac2;
 SELECT testTime='4000', Duration = DATEDIFF(ms,@StartTime,GETDATE());
GO
--===== Test Variable Assignment 1,000,000 times using NVARCHAR(MAX)
DECLARE @SomeString NVARCHAR(MAX),
        @StartTime DATETIME;
 SELECT @startTime = GETDATE();
 SELECT TOP 1000000
        @SomeString = 'ABC'
   FROM master.sys.all_columns ac1,
        master.sys.all_columns ac2;
 SELECT testTime='MAX', Duration = DATEDIFF(ms,@StartTime,GETDATE());
GO

4
那很有意思。在我的盒子上,最大MAX似乎慢了4倍。
stucampbell,2014年

4
SQL Server 2012的新结果:10比4k慢两倍,MAX比4k慢5.5倍。
cassandrad

1
大多数时间是从varchar隐式转换为nvarchar(max)。试试这个:DECLARE \ @SomeString NVARCHAR(MAX),\ @abc NVARCHAR(max)= N'ABC',\ @StartTime DATETIME; SELECT @startTime = GETDATE(); SELECT TOP 1000000 \ @SomeString = \ @abc from master.sys.all_columns ac1,master.sys.all_columns ac2; SELECT testTime ='MAX',持续时间= DATEDIFF(ms,\ @ StartTime,GETDATE()); 必须在变量前插入\才能发布。
Kvasi

4
SSD上的SQL Server 2014:150、156、716(10、4000,MAX)。
Maxim 2016年

2
感谢您为此次讨论添加一些实数。我们通常会忘记构建测试用例是了解问题的最快方法。
David C

13

可以将其视为另一个安全级别。您可以设计没有外键关系的表-完全有效-并确保关联实体完全存在于业务层上。但是,外键被认为是良好的设计习惯,因为外键在业务层发生混乱时会增加另一个约束级别。字段大小限制也是如此,并且不使用varchar MAX。


8

不使用max或text字段的原因是,即使使用SQL Server Enterprise Edition ,也无法执行联机索引重建,即REBUILD WITH ONLINE = ON。


1
TEXT字段类型也有相同的限制,因此您仍应使用VARCHAR(MAX)而不是TEXT。
将剃须刀

因此,我们无法重建聚簇索引。在我们将列提取到自己的表中之前,这花费了我们大量的磁盘空间(我们无法将表锁定超过7秒)
Choco Smith

4

我发现的唯一问题是,我们在SQL Server 2005上开发应用程序,并且在一个实例中,我们必须支持SQL Server2000。我刚刚了解到,SQL Server 2000不喜欢varchar或MAX选项的困难方式。 nvarchar。


2
那么,为什么不仅仅发展最低的公分母呢?
宾基

4

当您知道该字段将在5到10个字符的设定范围内时,这是个坏主意。我想如果不确定长度是多少,只会使用max。例如,电话号码永远不会超过一定数量的字符。

可以诚实地说,您不确定表中每个字段的近似长度要求吗?

不过,我的意思是正确的-我肯定会考虑使用varchar(max)的某些字段。

有趣的是,MSDN文档对其进行了很好的总结:

当列数据条目的大小相差很大时,请使用varchar。当列数据条目的大小相差很大,并且大小可能超过8,000个字节时,请使用varchar(max)。

关于这个问题,一个有趣的讨论


2
对于电话号码之类的东西,我更愿意使用char字段而不是varchar。只要您保持存储中的标准,并且不必担心来自不同国家/地区的电话号码,就永远不需要变量字段来存储电话号码(10个未格式化)或邮政编码(5个)或9-10(如果加上最后四位数字),等等
。– TheTXI

我指的是长度不等的电话号码。也许我应该把这个答案。任何固定长度的东西我都会使用char字段。
RichardOD

也许我应该在评论nchar或char中说。:-)
RichardOD

2
电话号码中的字符数几乎是一项业务要求。如果要求您将国际标准代码与数字一起存储,则可能会超过10。或者,世界上某些地区的电话号码可能有10位数以上。想象一下从IPV4到IPV6过渡的情况。没有人会争辩说,在IPV4的旧版本中,我们需要超过12位数字。如果IPV6流行起来,它可能无法保持良好状态。这又是一段时间内的业务规则更改。就像说的那样,变化是我们可以期待的唯一不变的事情:)
Pencilslate

2
请谨慎假设您知道电话号码字段中可以包含多少个字符,或者它们将属于哪种字符。除非系统使用该数据实际拨出(在这种情况下,您必须严格限制格式),否则用户可能会在其中合理地放入长字符串,例如“ 0123 456 78910请求接收分机号45,然后转移给James”。
文斯·鲍德伦

4

数据库的工作是存储数据,以便企业可以使用它。使数据有用的一部分是确保数据有意义。允许某人输入无限数量的姓氏并不能确保有意义的数据。

在业务层中建立这些约束是一个好主意,但这并不能确保数据库保持完整。保证不违反数据规则的唯一方法是在数据库中尽可能最低的级别实施它们。


6
IMO,数据长度限制完全基于业务规则,随着应用程序的增长,业务规则可能会在一段时间内发生变化。在业务逻辑上更改业务规则比在数据库级别上更容易。所以,我觉得应该分贝足够灵活,不应该与业务规则,如最大允许的名字,这是非常依赖于世界,你的一部分的长度在您的用户人死亡。
pencilslate

3

一个问题是,如果您必须使用多个版本的SQL Server,则MAX不会始终有效。因此,如果您使用的是传统数据库或涉及多个版本的任何其他情况,则最好格外小心。


我认为OP的潜行假设是他将完全处理2005+实例,并且他的应用程序不需要在2000(或更低版本)的版本上运行。不过,如果需要支持旧版本,我完全同意!
约翰·鲁迪

约翰·鲁迪(John Rudy):我想是这样的,我只知道当我以为自己不想去的时候,自己就遇到了那些障碍。
TheTXI

实际上,由于SQL CE 4不支持MAX列类型,这仍然是现代问题的普遍问题,因此互操作性很麻烦。
JohnC 2012年

3

如上所述,这主要是存储和性能之间的权衡。至少在大多数情况下。

但是,在选择n / varchar(Max)而不是n / varchar(n)时,还应考虑至少一个其他因素。数据是否要建立索引(例如姓氏)?由于MAX定义被认为是LOB,因此任何定义为MAX的内容均不可用于索引。在没有索引的情况下,所有涉及WHERE子句中谓词的数据的查找都将被强制进行全表扫描,这对于数据查找而言可能是最差的性能。


2

1)处理nvarchar(max)与nvarchar(n)时,SQL服务器将不得不利用更多的资源(分配的内存和cpu时间),其中n是特定于该字段的数字。

2)这对性能意味着什么?

在SQL Server 2005上,我从具有15个nvarchar(max)列的表中查询了13,000行数据。我对查询反复计时,然后将列更改为nvarchar(255)或更小。

优化之前的平均查询时间为2.0858秒。更改后的查询平均返回1.90秒。与基本的select *查询相比,这大约提高了184毫秒。那是8.8%的改善。

3)我的结果与其他几篇文章一致,这些文章表明两者之间存在性能差异。根据您的数据库和查询,改进百分比可能会有所不同。如果您没有很多并发用户或很多记录,那么性能差异对您来说就不是问题。但是,性能差异将随着更多记录和并发用户的增加而增加。


1

我有一个udf,它会填充字符串并将输出放入varchar(max)。如果直接使用此方法而不是将其转换为要调整的色谱柱的合适尺寸,则性能会很差。我最终将udf放到带有大音符的任意长度上,而不是依靠udf的所有调用方将字符串重新投射为较小的大小。


1

旧版系统支持。如果您有一个正在使用数据的系统,并且预期长度是一定的,则数据库是实施长度的好地方。这不是理想的选择,但是传统系统有时并不理想。= P


1

如果一行中的所有数据(对于所有列)都不会合理地占用8000个或更少的字符,则数据层的设计应强制执行此操作。

数据库引擎效率更高,可以将所有内容都排除在Blob存储之外。您可以限制行越小越好。您可以在页面中填充的行越多越好。当数据库必须访问较少的页面时,其性能会更好。


1

我的测试表明选择时存在差异。

CREATE TABLE t4000 (a NVARCHAR(4000) NULL);

CREATE TABLE tmax (a NVARCHAR(MAX) NULL);

DECLARE @abc4 NVARCHAR(4000) = N'ABC';

INSERT INTO t4000
SELECT TOP 1000000 @abc4
    FROM
    master.sys.all_columns ac1,
    master.sys.all_columns ac2;

DECLARE @abc NVARCHAR(MAX) = N'ABC';

INSERT INTO tmax
SELECT TOP 1000000 @abc
    FROM
    master.sys.all_columns ac1,
    master.sys.all_columns ac2;

SET STATISTICS TIME ON;
SET STATISTICS IO ON;

SELECT * FROM dbo.t4000;
SELECT * FROM dbo.tmax;

0

有趣的链接: 为什么可以在使用TEXT时使用VARCHAR?

它是关于PostgreSQL和MySQL的,所以性能分析是不同的,但是“显式性”的逻辑仍然成立:为什么要强迫自己始终担心与某事有关的事情呢?如果您将电子邮件地址保存到变量,则将使用“字符串”而不是“限于80个字符的字符串”。


1
这类似于认为您不应该具有检查约束来确保一个人的年龄不是负数。
乔纳森·艾伦

我发现数据正确性和性能优化之间存在差异。
orip

0

我可以看到的主要缺点是,假设您有以下内容:

哪一项能为您提供有关UI所需数据的最多信息?

这个

            CREATE TABLE [dbo].[BusData](
                [ID] [int] IDENTITY(1,1) NOT NULL,
                [RecordId] [nvarchar](MAX) NULL,
                [CompanyName] [nvarchar](MAX) NOT NULL,
                [FirstName] [nvarchar](MAX) NOT NULL,
                [LastName] [nvarchar](MAX) NOT NULL,
                [ADDRESS] [nvarchar](MAX) NOT NULL,
                [CITY] [nvarchar](MAX) NOT NULL,
                [County] [nvarchar](MAX) NOT NULL,
                [STATE] [nvarchar](MAX) NOT NULL,
                [ZIP] [nvarchar](MAX) NOT NULL,
                [PHONE] [nvarchar](MAX) NOT NULL,
                [COUNTRY] [nvarchar](MAX) NOT NULL,
                [NPA] [nvarchar](MAX) NULL,
                [NXX] [nvarchar](MAX) NULL,
                [XXXX] [nvarchar](MAX) NULL,
                [CurrentRecord] [nvarchar](MAX) NULL,
                [TotalCount] [nvarchar](MAX) NULL,
                [Status] [int] NOT NULL,
                [ChangeDate] [datetime] NOT NULL
            ) ON [PRIMARY]

或这个?

            CREATE TABLE [dbo].[BusData](
                [ID] [int] IDENTITY(1,1) NOT NULL,
                [RecordId] [nvarchar](50) NULL,
                [CompanyName] [nvarchar](50) NOT NULL,
                [FirstName] [nvarchar](50) NOT NULL,
                [LastName] [nvarchar](50) NOT NULL,
                [ADDRESS] [nvarchar](50) NOT NULL,
                [CITY] [nvarchar](50) NOT NULL,
                [County] [nvarchar](50) NOT NULL,
                [STATE] [nvarchar](2) NOT NULL,
                [ZIP] [nvarchar](16) NOT NULL,
                [PHONE] [nvarchar](18) NOT NULL,
                [COUNTRY] [nvarchar](50) NOT NULL,
                [NPA] [nvarchar](3) NULL,
                [NXX] [nvarchar](3) NULL,
                [XXXX] [nvarchar](4) NULL,
                [CurrentRecord] [nvarchar](50) NULL,
                [TotalCount] [nvarchar](50) NULL,
                [Status] [int] NOT NULL,
                [ChangeDate] [datetime] NOT NULL
            ) ON [PRIMARY]

3
我希望业务逻辑告诉我公司名称最多可以包含50个字符,而不是依赖数据库表获取该信息。
可汗

我同意杰夫的观点。我认为持久性存储不是定义业务规则的正确位置。并且在分层体系结构中,您的UI甚至都不了解持久层。
stucampbell

当然,除非您使用的值限制为特定大小,例如国家/地区的ISO代码。
2012年

2
与表def有什么关系?您仍然可以具有业务逻辑。我认为您的观点与表格的设计无关。如果您仍然想在业务层中设计某种定义,那就去吧。尽管更有意义,但无论如何还是在业务层中使用存储的proc;没有表def?
卡洛斯·马丁尼

1
似乎不受欢迎,但我同意carlos的看法,如果数据库设置了最大大小,那么您可以在可能需要处理的基础上的所有层上感到舒服。如果您有多个系统要写入数据库,那么这尤其重要。
蒂姆·艾贝尔

0

一个缺点是您将围绕不可预测的变量进行设计,并且您可能会忽略而不是利用内部SQL Server数据结构,而该内部SQL Server数据结构逐渐由行,页面和范围组成。

这让我想到了数据结构对齐 C语言中的,并且意识到对齐方式通常被认为是一件好事(TM)。相似的想法,不同的上下文。

的MSDN页面 页面和范围的

行溢出数据的 MSDN页面


0

首先,我考虑了一下,然后又想了。这涉及性能,但是同样可以作为一种文档形式来了解字段的实际大小。当该数据库位于更大的生态系统中时,它确实会强制执行。我认为关键是要允许,但只能在合理范围内。

好的,这只是我对业务和数据层逻辑问题的感觉。这取决于,如果您的数据库是共享业务逻辑的系统之间的共享资源,那么执行该逻辑当然是很自然的地方,但这不是执行此逻辑的最佳方式,最佳方式是提供一个API,这允许进行测试的交互,并将业务逻辑保持在其所属位置,使系统保持解耦,使系统中的各个层保持解耦。但是,如果您的数据库仅应服务于一个应用程序,那么请让AGILE进行思考,现在情况如何?现在设计。如果以及何时需要这种访问,请提供该数据的API。

显然,这只是理想选择,如果您正在使用现有系统,则很有可能至少在短期内需要以不同的方式进行操作。


-1

这将导致性能问题,尽管如果数据库很小,则可能永远不会导致任何实际问题。每条记录将占用硬盘驱动器上的更多空间,并且如果您一次搜索大量记录,则数据库将需要读取磁盘的更多扇区。例如,一条小记录可以容纳50个扇区,大记录可以容纳5个扇区。使用大记录,您需要从磁盘读取10倍的数据。


5
-1。nvarchar(max)列中存储的长度为100的字符串所占用的磁盘空间不会比列中的磁盘空间多nvarchar(100)
马丁·史密斯,

如果存储的数据的大小更大,那么您所描述的是正确的,但是这个问题是关于数据类型是否影响性能或其他考虑因素。

-2

这将使屏幕设计更加困难,因为您将无法再预测控件的宽度。

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.