总是使用nvarchar(MAX)有什么缺点吗?

时间:2008-09-29 12:01:12

标签: sql sql-server sql-server-2005

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

24 个答案:

答案 0 :(得分:139)

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

从原帖(更多信息):

  

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

     

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

答案 1 :(得分:47)

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

缺点可能包括:

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

“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

答案 2 :(得分:28)

有时您希望数据类型对其中的数据强制执行。

例如,假设您的列不应超过20个字符。如果您将该列定义为VARCHAR(MAX),则某些恶意应用程序可能会在其中插入一个长字符串,您将永远不知道,或者有任何方法可以阻止它。

下次您的应用程序使用该字符串时,假设字符串的长度对于它所代表的域而言是适度且合理的,您将会遇到不可预测且令人困惑的结果。

答案 3 :(得分:23)

根据接受答案中提供的链接,显示:

  1. nvarchar(MAX)字段中存储的100个字符将与nvarchar(100)字段中的100个字符存储不同 - 数据将以内联方式存储,您将无法获得阅读和写数据“不合时宜”。所以不用担心。

  2. 如果大小超过4000,数据将自动存储在“行外”,这就是您想要的。所以也不用担心。

  3. ...然而

    1. 您无法在nvarchar(MAX)列上创建索引。您可以使用全文索引,但无法在列上创建索引以提高查询性能。对我来说,这封印了这笔交易......总是使用nvarchar(MAX)是一个明显的缺点。
    2. 结论:

      如果您想在整个数据库中使用一种“通用字符串长度”(可以编制索引,并且不会浪费空间和访问时间),那么您可以使用nvarchar(4000)

答案 4 :(得分:19)

我检查了一些文章,并从中找到了有用的测试脚本: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

答案 5 :(得分:12)

将其视为另一个安全级别。您可以设计没有外键关系的表 - 完全有效 - 并确保完全在业务层上存在关联实体。但是,外键被认为是很好的设计实践,因为它们会增加另一个约束级别,以防万一在业务层上出现问题。字段大小限制也是如此,不使用varchar MAX。

答案 6 :(得分:8)

不使用max或text字段的原因是,即使使用SQL Server Enterprise Edition,也无法执行online index rebuilds,即REBUILD WITH ONLINE = ON。

答案 7 :(得分:4)

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

答案 8 :(得分:4)

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

你能否诚实地说你对表中每个字段的大致长度要求都不确定?

我确实明白了 - 有一些我肯定会考虑使用varchar(max)的字段。

有趣的是MSDN docs总结得很好:

  

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

an interesting discussion on the issue here

答案 9 :(得分:4)

数据库的工作是存储数据,以便企业可以使用它。使数据有用的部分原因是确保它有意义。允许某人为其名字输入无限数量的字符并不能确保有意义的数据。

将这些约束构建到业务层是一个好主意,但这并不能确保数据库保持不变。保证数据规则不被违反的唯一方法是在数据库中尽可能最低级别强制执行它们。

答案 10 :(得分:3)

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

但是,在n / varchar(n)上选择n / varchar(Max)时,至少应考虑另外一个因素。数据是否会被索引(例如,姓氏)?由于MAX定义被认为是LOB,因此定义为MAX的任何内容都不可用于索引。如果没有索引,任何涉及数据作为WHERE子句中的谓词的查找都将被强制进入全表扫描,这是数据查找的最差性能。

答案 11 :(得分:3)

一个问题是,如果您必须使用多个版本的SQL Server,MAX将无法始终工作。因此,如果您正在处理遗留数据库或涉及多个版本的任何其他情况,您最好小心。

答案 12 :(得分:2)

截至 SQL Server 2019,NVARCHAR(MAX)仍然不支持 SCSU“Unicode 压缩”——即使使用行内数据存储进行存储。 SCSU 是在 SQL Server 2008 中添加的,适用于任何 ROW/PAGE 压缩表和索引。

因此,NVARCHAR(MAX) 可以占用两倍于具有相同文本内容的 NVARCHAR(1..4000) 字段的物理磁盘空间< sup>+ — 即使 not 存储在 LOB 中。非 SCSU 浪费取决于表示的数据和语言。

Unicode Compression Implementation

<块引用>

SQL Server 使用 Unicode 标准压缩方案 (SCSU) 算法的实现来压缩存储在行或页压缩对象中的 Unicode 值。对于这些压缩对象,Unicode 压缩对于 nchar(n) 和 nvarchar(n) 列是自动的 [并且从未与 nvarchar(max) 一起使用]。

另一方面,PAGE 压缩(自 2014 年起)仍然适用于 NVARCHAR(MAX) 列如果它们被写为行内数据......所以缺少 SCSU 感觉就像是“缺少优化”。与 SCSU 不同的是,页面压缩结果可能会因共享的前导前缀(即重复值)而有很大差异。

然而,由于避免了隐式转换,使用 NVARCHAR(MAX) 可能仍然“更快”,即使 OPENJSON 等函数的 IO 成本更高。这是隐式转换开销,取决于使用的相对成本以及是否在过滤之前或之后触及该字段。在 VARCHAR(MAX) 列中使用 2019 年的 UTF-8 归类时,也存在同样的转换问题。

使用 NVARCHAR(1-4000) 还需要 ~8000 字节行配额的 N*2 字节,而 NVARCHAR(MAX) 仅需要 24 字节。整体设计和使用需要一起考虑,以说明具体的实现细节。

+在我的数据库/数据/模式中,通过使用两列(读取时合并),可以将磁盘空间使用量减少约 40%,同时仍然支持溢出的文本值。 SCSU 虽然存在缺陷,但它是一种非常聪明且未被充分利用的方法,可以更有效地存储 Unicode。

答案 13 :(得分:2)

1)在处理nvarchar(max)vs 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)我的结果与其他一些文章同时表明存在性能差异。根据您的数据库和查询,改进的百分比可能会有所不同。如果您没有大量并发用户或非常多的记录,那么性能差异对您来说不是问题。但是,随着更多记录和并发用户的增加,性能差异将会增加。

答案 14 :(得分:1)

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

数据库引擎可以更有效地防止blob存储中的所有内容。您可以越小越好。您可以在页面中填充的行越多越好。当数据库必须访问较少的页面时,数据库的表现会更好。

答案 15 :(得分:1)

遗留系统支持。如果您的系统正在使用数据并且预计它具有一定的长度,则数据库是强制执行长度的好地方。这并不理想,但遗留系统有时并不理想。 = P

答案 16 :(得分:1)

有趣的链接:Why use a VARCHAR when you can use TEXT?

这是关于PostgreSQL和MySQL,所以性能分析是不同的,但“显性”的逻辑仍然存在:为什么要强迫自己总是担心在一小部分时间内相关的事情?如果您将电子邮件地址保存到变量,则使用“字符串”而不是“限制为80个字符的字符串”。

答案 17 :(得分: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;

答案 18 :(得分:1)

我有一个udf填充字符串并将输出放到varchar(max)。如果直接使用它而不是回流到适合调整的色谱柱的尺寸,则性能非常差。我最终把udf放到一个带有大音符的任意长度,而不是依赖于udf的所有调用者将字符串重新转换为更小的大小。

答案 19 :(得分: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]

答案 20 :(得分:0)

一个缺点是你将围绕一个不可预测的变量进行设计,你可能会忽略而不是利用内部SQL Server数据结构,逐步由Row(s),Page(s)和Extent组成( S)。

这使我想到C中的data structure alignment,并且知道对齐通常被认为是Good Thing(TM)。类似的想法,不同的背景。

Pages and Extents

的MSDN页面

Row-Overflow Data

的MSDN页面

答案 21 :(得分:0)

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

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

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

答案 22 :(得分:-1)

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

答案 23 :(得分:-3)

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