我想在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)
这样做有助于保持表格大小更小?或者我只想尝试智能数据库而不实现任何目标?
答案 0 :(得分:27)
保持内联。自SQL 2005以来,SQL Server已将MAX列存储在单独的“分配单元”中。请参阅Table and Index Organization。这实际上与将MAX列保留在自己的表中完全相同,但没有明确这样做的任何缺点。
拥有一个显式表实际上是慢(因为外键约束)和消耗更多空间(因为DetaiID重复)。更不用说它需要更多代码,并且......编写代码会引入错误。
<强>更新强>
要检查数据的实际位置,可以使用简单的测试显示:
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分配单元的Textpointer:
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
选项可以免费获得开发/测试,从而获得相同的结果。
答案 1 :(得分:10)
矛盾的是,如果你的数据通常少于8000个字符,我会将它存储在一个单独的表中,而如果数据大于8000个字符,我会将它保存在同一个表中。
这是因为如果SQL允许行位于单页中,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)。
答案 2 :(得分:2)
您应该将数据结构化为最合乎逻辑的结构,并允许SQL Server对如何物理存储数据执行优化。
如果通过性能分析发现您的结构存在性能问题,请考虑对结构或存储设置进行更改。
答案 3 :(得分:0)
保持内联。 varchar
的全部意义在于,如果它为空则占用0个字节,为“Hello”占用4个字节,依此类推。
答案 4 :(得分:0)
我会通过创建Detail表来规范化它。我假设Log中的一些条目具有相同的细节?因此,如果将其标准化,则只有在将详细信息存储在详细信息表上时,才会存储FK id INTEGER而不是每次出现的文本。如果你有理由去标准化那么做,但从你的问题我不认为是这种情况。
答案 5 :(得分:0)
拥有一个可为空的列,每16个就需要2个字节。如果这是表中唯一(或第17或第33等)可空列,则每行将花费2个字节,否则无效。