我应该使用nvarchar(max)替换nvarchar(64)列还是作为附加列?

4

我正在为追踪数据库中特定对象的历史记录建立一张表格。 目前我有以下列:

HistoryId int IDENTITY(1,1) NOT NULL
HistoryDate datetimeoffset(7) NOT NULL 
HistoryTypeId int NOT NULL
HistoryDetails nvarchar(max) NULL

在大多数情况下,每个历史记录项都可以通过HistoryTypeId自我解释,因此HistoryDetails将为Null或非常小。但对于一些历史类型,详细数据将会很大。是否应该对所有记录使用nvarchar(max),还是应该将其分开,并为需要超过64个字符的历史类型添加额外的列(如下所示)?粗略估计是80%-90%的记录不需要超过64个字符的详细信息,并且表中将有数百万条记录。
HistoryId int IDENTITY(1,1) NOT NULL
HistoryDate datetimeoffset(7) NOT NULL 
HistoryTypeId int NOT NULL
HistoryDetails nvarchar(64) NULL
HistoryDetailsMore nvarchar(max) NULL

请问您能否发布一条典型的针对您的表的查询语句? - Quassnoi
Quassnoi:你的计算列解决方案正是我想要的。我的典型查询如下: SELECT UserId,HistoryDate,HistoryTypeId,HistoryDetails FROM History WHERE UserId=XXX 因此,我希望获取所有HistoryTypeIds的HistoryDetails,但在大多数情况下只需要前64个字符(否则将通过第二个查询处理)。 - Jeff Widmer
3个回答

5
您不能将NVARCHAR(MAX)作为普通B-Tree索引的一部分(但您仍然可以将其用作索引中的包含列)。
否则,只要该列中的数据未超过行大小阈值,存储将保持不变。
由于您可能不会对此字段进行索引,因此最好将其创建为NVARCHAR(MAX)
即使您仍想对其进行索引(例如,使用LIKE进行前缀搜索),也可以创建一个计算的NVARCHAR(450)列,在该列上创建索引,并将其添加到查询中以进行粗略过滤。
有关更多详细信息,请参见我的博客中的此条目: 如果您只要进行小列的精确搜索,请创建一个计算列,对其进行索引并像这样查询:
ALTER TABLE History ADD HistoryDetailsIndex AS SUBSTRING(HistoryDetails, 1, 50)

CREATE INDEX ix_mytable_typeid_details ON History (HistoryTypeId, HistoryDetailsIndex) INCLUDE (HistoryDetails)

SELECT  COUNT(*)
FROM    History
WHERE   HistoryTypeId = 123
        AND HistoryDetailsIndex LIKE 'string_prefix_up_to_50_characters%'
        AND HistoryDetails = 'string_prefix_up_to_50_characters_plus_everything_after_it'

这将只包括您的HistoryDetails中的前50个字符作为索引键(将在LIKE条件下搜索),并将所有内容放入included列中。
如果您绝对确定永远不会搜索超过50个字符长的字符串,可以省略included列,只使用以下内容:
SELECT  COUNT(*)
FROM    History
WHERE   HistoryTypeId = 123
        AND HistoryDetailsIndex = 'string_prefix_up_to_50_characters'

这将使索引变短。

但是,如果您提供的字符串长度超过50个字符,则此方法将失败,因此仅在绝对确定不会搜索长字符串时使用它。


如果需要nvarchar(max)的项目超过了行大小阈值,那会怎样呢?这会改变什么吗? - Jeff Widmer
在内部,此列将被存储为行外数据 (http://msdn.microsoft.com/en-us/library/ms186981.aspx)。从用户的角度来看,没有任何变化:您可以像往常一样使用该列。 - Quassnoi
所以根据你的说法(以及你的博客文章),由于在该列中具有小值的记录占80-90%需要进行索引,但是具有大值的10%不需要进行索引,因此我应该选择双列架构。其中一个是包含在索引中的nvarchar(64),另一个是非索引的nvarchar(max)。你能确认一下吗? - Jeff Widmer
你会用索引来做什么?如果是为了前缀搜索,只需将数据保留在 NVARCHAR(MAX) 中,并创建一个计算列 NVARCHAR(450)。这个计算列实际上会存储和索引数据的前 450 个字符,通常足以满足任何前缀搜索的需求。如果你使用索引来避免 key lookups / RID lookups,那么只需创建一个包含 NVARCHAR(MAX) 的索引列即可。这个索引将不允许对字符串进行前缀查找,但如果你在查询中只使用索引覆盖的列,它仍然可以从索引中获取所有数据。 - Quassnoi
我会在这种情况下使用索引:SELECT COUNT(*) FROM History WHERE HistoryTypeId=123 AND HistoryDetails='XYZ',但仅针对80-90%在该列中具有小值的记录。 - Jeff Widmer

0

由于您正在使用nvarchar,除非SQLServer针对小情况覆盖变量长度,否则您已经支付了可变长度记录开销。但是,磁盘上的空间不应该因nvarchar(64)和nvarchar(max)之间的短记录而改变。它们应该只占用适合其数据的空间。通常,该数字仅用于约束数据。如果您不想对其进行约束,则不应在使用这两个之间支付罚款。


那么你的意思是只使用单个HistoryDetails nvarchar(max)列? - Jeff Widmer
我想,除非您正在对该列进行索引或定期编辑这些字段,否则这将是比创建单独的表更好的解决方案。 - Chris J

0

首先要知道,varchar(MAX) 可以存储高达 2GB 的空间,实际上它使用的是 TEXT 值,在处理时比 varchar(8000) 或更少的值需要更多的处理。

如果您在 varchar(max) 中存储了大量较小的数据,则它将被视为普通的 varchar 列,除非您超过了 8000,此后它将被视为 varchar(max)。

该列是否已经建立索引,或者您想要对其进行索引?如果是这样,请避免使用 varchar(max)。

我建议只需选择一个较高的值,例如 varchar(255),并强制用户适应您的数据库设计而不是相反。


你对索引的评论与Quassnoi的答案不符。我将对该列进行索引,但仅因为该列中的项目类型具有较小的数据量(占记录的80-90%)。 - Jeff Widmer

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接