【问题标题】:Should I use an inline varchar(max) column or store it in a separate table?我应该使用内联 varchar(max) 列还是将其存储在单独的表中?
【发布时间】:2010-12-14 16:20:29
【问题描述】:

我想在 MS SQL Server 2005 中创建一个表来记录某些系统操作的详细信息。从下面的表格设计中可以看出,除了Details 之外的每一列都是不可为空的。

CREATE TABLE [Log]
(
[LogID] [int] IDENTITY(1,1) NOT NULL,
[ActionID] [int] NOT NULL,
[SystemID] [int] NOT NULL,
[UserID] [int] NOT NULL,
[LoggedOn] [datetime] NOT NULL,
[Details] [varchar](max) NULL
)

因为Details 列中并不总是有数据。将此列存储在单独的表中并提供指向它的链接是否更有效?

CREATE TABLE [Log]
(
[LogID] [int] IDENTITY(1,1) NOT NULL,
[ActionID] [int] NOT NULL,
[SystemID] [int] NOT NULL,
[UserID] [int] NOT NULL,
[LoggedOn] [datetime] NOT NULL,
[DetailID] [int] NULL
)       

CREATE TABLE [Detail]
(
[DetailID] [int] IDENTITY(1,1) NOT NULL,
[Details] [varchar](max) NOT NULL
)

对于较小的数据类型,我不会真正考虑它,但对于varchar(max),这样做是否有助于保持表大小更小?还是我只是想使数据库变得聪明而一无所获?

【问题讨论】:

    标签: sql-server normalization varchar


    【解决方案1】:

    保持内联。 varchar 的全部意义在于,如果它为空则占用 0 个字节,'Hello' 占用 4 个字节,依此类推。

    【讨论】:

    【解决方案2】:

    我会通过创建详细信息表对其进行规范化。我假设 Log 中的某些条目将具有相同的详细信息?因此,如果您对其进行规范化,如果您将文本存储在 Detail 表中,那么您将只存储 FK id INTEGER 而不是每次出现的文本。如果您有理由去规范化,请执行此操作,但从您的问题来看,我认为情况并非如此。

    【讨论】:

    • @StarShip3000,您是否建议将详细信息表作为一个维度表,他存储要链接到的预定义详细信息值?如果是这样,我认为这对于日志表不实用(因为他的详细信息需要是 varchar(max),因此可能无法预定义详细信息)。我认为他是在问是否只是将表格分成两个一对一的部分,这实际上并不是一个规范化问题,只是一个页面级存储问题。
    • 我建议只有在值重复且全部已知的情况下才这样做。如果它是一个通用的日志类型,我不建议这样做。
    【解决方案3】:

    矛盾的是,如果您的数据通常少于 8000 个字符,我会将其存储在单独的表中,而如果数据大于 8000 个字符,我会将其保存在同一个表中。

    这是因为如果 SQL Server 允许该行位于单个页面中,则 SQL Server 会将数据保留在页面中,但是当数据变大时,它会像 TEXT 数据类型一样将其移出并只留下一个行中的指针。所以对于一堆 3000 个字符的行,你每页拟合的行数更少,这确实是低效的,但是对于一堆 12000 个字符的行,数据在行外,所以实际上效率更高。

    话虽如此,通常您的长度组合范围很广,因此我会将其移至自己的表格中。这使您可以灵活地将此表移动到不同的文件组等。

    请注意,您 can also specify it to force the data out of the row 使用 sp_tableoption。 varchar(max) 基本上类似于 TEXT 数据类型,它默认为行内数据(对于 varchar(max))而不是默认为行外数据(对于 TEXT)。

    【讨论】:

    • 我以为 TEXT、IMAGE 和好友默认存储在行外?
    • TEXT 默认存储在行外,而 varchar(max) 默认存储在行中。除此之外,它们在存储方面的行为非常相似。
    【解决方案4】:

    您应该将数据构建成看起来最合乎逻辑的结构,并允许 SQL Server 对其如何物理存储数据进行优化。

    如果您通过性能分析发现您的结构存在性能问题,请考虑对您的结构或存储设置进行更改。

    【讨论】:

      【解决方案5】:

      保持内联。自 SQL 2005 以来,SQL Server 已经将 MAX 列存储在单独的“分配单元”中。请参阅Table and Index Organization。这实际上与将 MAX 列保留在其自己的表中完全相同,但没有显式这样做的任何缺点。

      拥有一个显式表实际上会更慢(因为外键约束)和消耗更多空间(因为 DetailID 重复)。更不用说它需要更多的代码,而且通过......编写代码引入了错误。

      alt text http://i.msdn.microsoft.com/ms189051.3be61595-d405-4b30-9794-755842d7db7e(en-us,SQL.100).gif

      更新

      要查看数据的实际位置,一个简单的测试就可以显示出来:

      use tempdb;
      go
      
      create table a (
        id int identity(1,1) not null primary key,
        v_a varchar(8000),
        nv_a nvarchar(4000),
        m_a varchar(max),
        nm_a nvarchar(max),
        t text,
        nt ntext);
      go
      
      insert into a (v_a, nv_a, m_a, nm_a, t, nt)
      values ('v_a', N'nv_a', 'm_a', N'nm_a', 't', N'nt');
      go
      
      select %%physloc%%,* from a
      go
      

      %%physloc%% 伪列将显示行的实际物理位置,在我的例子中是第 200 页:

      dbcc traceon(3604)
      dbcc page(2,1, 200, 3)
      
      Slot 0 Column 2 Offset 0x19 Length 3 Length (physical) 3
      v_a = v_a                            
      Slot 0 Column 3 Offset 0x1c Length 8 Length (physical) 8
      nv_a = nv_a                          
      m_a = [BLOB Inline Data] Slot 0 Column 4 Offset 0x24 Length 3 Length (physical) 3
      m_a = 0x6d5f61                       
      nm_a = [BLOB Inline Data] Slot 0 Column 5 Offset 0x27 Length 8 Length (physical) 8
      nm_a = 0x6e006d005f006100            
      t = [Textpointer] Slot 0 Column 6 Offset 0x2f Length 16 Length (physical) 16
      TextTimeStamp = 131137536            RowId = (1:182:0)                    
      nt = [Textpointer] Slot 0 Column 7 Offset 0x3f Length 16 Length (physical) 16
      TextTimeStamp = 131203072            RowId = (1:182:1)   
      

      除 TEXT 和 NTEXT 之外的所有列值都内联存储,包括 MAX 类型。
      更改表选项并插入新行(sp_tableoption 不会影响现有行)后,MAX 类型被逐出到自己的存储中:

      sp_tableoption 'a' , 'large value types out of row', '1';
      insert into a (v_a, nv_a, m_a, nm_a, t, nt)
      values ('2v_a', N'2nv_a', '2m_a', N'2nm_a', '2t', N'2nt');    
      dbcc page(2,1, 200, 3);
      

      注意 m_a 和 nm_a 列现在如何成为 LOB 分配单元的文本指针:

      Slot 1 Column 2 Offset 0x19 Length 4 Length (physical) 4
      v_a = 2v_a                           
      Slot 1 Column 3 Offset 0x1d Length 10 Length (physical) 10
      nv_a = 2nv_a                         
      m_a = [Textpointer] Slot 1 Column 4 Offset 0x27 Length 16 Length (physical) 16
      TextTimeStamp = 131268608            RowId = (1:182:2)                    
      nm_a = [Textpointer] Slot 1 Column 5 Offset 0x37 Length 16 Length (physical) 16
      TextTimeStamp = 131334144            RowId = (1:182:3)                    
      t = [Textpointer] Slot 1 Column 6 Offset 0x47 Length 16 Length (physical) 16
      TextTimeStamp = 131399680            RowId = (1:182:4)                    
      nt = [Textpointer] Slot 1 Column 7 Offset 0x57 Length 16 Length (physical) 16
      TextTimeStamp = 131465216            RowId = (1:182:5)                    
      

      为了完整起见,我们还可以强制非最大字段之一:

      update a set v_a = replicate('X', 8000);
      dbcc page(2,1, 200, 3);
      

      注意 v_a 列是如何存储在 Row-Overflow 存储中的:

      Slot 0 Column 1 Offset 0x4 Length 4 Length (physical) 4
      v_a = [BLOB Inline Root] Slot 0 Column 2 Offset 0x19 Length 24 Length (physical) 24
      Level = 0                            Unused = 99                          UpdateSeq = 1
      TimeStamp = 1098383360               
      Link 0
      Size = 8000                          RowId = (1:176:0) 
      

      因此,正如其他人已经评论的那样,MAX 类型默认情况下是内联存储的,如果它们适合的话。对于许多 DW 项目,这将是不可接受的,因为典型的 DW 加载必须扫描或至少范围扫描,因此应使用 sp_tableoption ..., 'large value types out of row', '1'。请注意,这不会影响现有行,在我的测试中甚至在索引重建时也没有,因此必须尽早打开该选项。

      对于大多数 OLTP 类型加载,尽管 MAX 类型尽可能内联存储这一事实实际上是一个优势,因为 OLTP 访问模式是查找,而行宽对其影响不大。

      不过,关于原始问题:不需要单独的表格。开启large value types out of row 选项可以免费获得相同的开发/测试结果。

      【讨论】:

      • +! SQL Server 会在单独的分配单元中存储 17 个字符的 varchar(max) 吗?
      • 默认行为是它不会将 varchar(max) 存储到 LoB 存储/分配中,除非您超出分配,Cade Roux 发布了一个指向设置的链接以更改此设置。
      • 非常详细 - 感谢您的完整 Remus
      【解决方案6】:

      每 16 个可空列需要 2 个字节。如果这是表中唯一(或第 17 或第 33 等)可为空的列,则每行将花费您 2 个字节,否则没有。

      【讨论】:

        猜你喜欢
        • 2018-05-04
        • 1970-01-01
        • 2012-07-05
        • 1970-01-01
        • 2010-11-29
        • 2014-05-06
        • 1970-01-01
        • 2021-06-27
        • 1970-01-01
        相关资源
        最近更新 更多