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

在SQL Server 2005中,将所有字符字段设置为nvarchar(MAX)而不是显式指定长度(例如nvarchar(255))有什么缺点吗?(除了不能在数据库级别限制字段长度之外)

171676 次浏览

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

这是一个公平的问题,他除了明显的……

缺点包括:

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

< p > " 1。数据库扩展和页面的空间分配是灵活的。因此,当使用update向字段添加信息时,如果新数据比之前插入的数据长,数据库就必须创建一个指针。这样,数据库文件将变得碎片化=从索引到删除、更新和插入,几乎所有方面的性能都较低。” http://sqlblogcasts.com/blogs/simons/archive/2006/02/28/Why-use-anything-but-varchar_2800_max_2900_.aspx < / p > 集成影响-其他系统很难知道如何与您的数据库集成 不可预测的数据增长 可能的安全问题,例如,占用所有磁盘空间

可能导致系统崩溃

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

同样的问题也出现在MSDN论坛上:

原文(更多信息):

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

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

我有一个udf填充字符串,并把输出varchar(max)。如果直接使用它,而不是将其转换回正在调整的列的适当大小,则性能非常差。我最终将udf设置为一个任意长度的大音符,而不是依赖udf的所有调用者将字符串重新转换为较小的大小。

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

这将使屏幕设计变得更加困难,因为你将不再能够预测你的控制应该有多宽。

有趣的链接:为什么使用VARCHAR当你可以使用文本?

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

一个问题是,如果你必须使用多个版本的SQL Server, MAX并不总是有效的。因此,如果您正在使用遗留DB或涉及多个版本的任何其他情况,您最好非常小心。

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

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

下次应用程序使用该字符串时,假设字符串的长度对于它所代表的领域来说是适度和合理的,那么您将体验到一个不可预测和令人困惑的结果。

当你知道字段将在一个固定的范围内时,这不是一个好主意——例如5到10个字符。我想我只会在不确定长度的情况下使用max。例如,电话号码永远不会超过一定数量的字符。

你能诚实地说,你不确定表中每个字段的大约长度要求吗?

我确实明白你的意思——有些字段我肯定会考虑使用varchar(max)。

有趣的是,MSDN文档很好地总结了它:

使用varchar当 列数据条目变化很大。 的大小时使用varchar(max) 列数据条目变化很大, 并且大小可能超过8000字节

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

把它当做另一个安全等级。您可以设计没有外键关系的表(完全有效),并确保完全在业务层上存在关联实体。然而,外键被认为是很好的设计实践,因为它们增加了另一个约束级别,以防业务层出现问题。同样,字段大小限制和不使用varchar MAX。

遗留系统支持。如果您有一个正在使用数据的系统,并且希望数据有一定的长度,那么数据库是执行该长度的好地方。这并不理想,但遗留系统有时也不理想。= P

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

数据库引擎可以更有效地将所有内容排除在blob存储之外。限制行越小越好。一页中可以塞进的行越多越好。当数据库必须访问更少的页面时,它的性能会更好。

不使用max或文本字段的一个原因是,即使在SQL Server企业版,你也不能执行在线索引重建,即REBUILD WITH ONLINE= ON。

数据库的任务是存储数据,以供企业使用。让数据有用的一部分是确保它是有意义的。允许用户为自己的名字输入无限个字符并不能确保数据有意义。

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

我能看到的主要缺点是,假设你有这样的情况:

哪一个提供了关于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]

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

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

页和区的MSDN页面

Row-Overflow数据的MSDN页面

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

2)就绩效而言,这意味着什么?

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

优化之前的查询平均为2.0858秒。更改后的查询平均返回时间为1.90秒。这比基本select *查询提高了约184毫秒。这一数字提高了8.8%。

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

我检查了一些文章,从中找到了有用的测试脚本:http://www.sqlservercentral.com/Forums/Topic1480639-1292-1.aspx 然后将其更改为NVARCHAR(10) vs NVARCHAR(4000) vs 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

根据已接受的答案中提供的链接,似乎是:

  1. 存储在nvarchar(MAX)字段中的100个字符将与存储在nvarchar(100)字段中的100个字符没有区别——数据将内联存储,并且您将不会有“行外”读取和写入数据的开销。所以不用担心。

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

然而……

  1. 不能在nvarchar(MAX)列上创建索引。可以使用全文索引,但不能在列上创建索引以提高查询性能。对我来说,这就决定了……总是使用nvarchar(MAX)是一个明显的缺点。

结论:

如果你想要一种贯穿整个数据库的“通用字符串长度”,它可以被索引,并且不会浪费空间和访问时间,那么你可以使用nvarchar(4000)

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

然而,在选择n/varchar(Max)而不是n/varchar(n)时,至少还有一个其他因素需要考虑。数据是否将被索引(例如,一个姓氏)?因为MAX定义被认为是LOB,所以任何定义为MAX的东西都不能用于索引。如果没有索引,在WHERE子句中涉及数据作为谓词的任何查找都将被迫进行全表扫描,这是您可以获得的数据查找的最差性能。

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

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;

起初我是这么想的,但后来又想了想。这样做会影响性能,但同样地,它也可以作为一种文档形式来了解字段的实际大小。当数据库位于更大的生态系统中时,它确实会强制执行。在我看来,关键是要在合理的范围内宽容。

好的,以下是我对业务和数据层逻辑问题的看法。这取决于,如果你的数据库是共享业务逻辑的系统之间的共享资源,那么它似乎是一个自然的地方来执行这样的逻辑,但这不是最好的方法,最好的方法是提供一个API,这允许交互被测试,并保持业务逻辑在它所属的地方,它保持系统解耦,它保持系统内的层解耦。然而,如果您的数据库应该只服务于一个应用程序,那么让我们考虑敏捷,现在什么是正确的?为现在设计。如果需要这样的访问,则为该数据提供API。

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

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

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

Unicode压缩实现:

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)也需要N*2个字节的~8000字节行配额,而NVARCHAR(MAX)只需要24个字节。总体设计和使用需要一起考虑,以考虑具体的实现细节。

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