SQL Server 2014压缩和最大行大小


8

我需要创建一个包含许多十进制(26,8)列的宽非规范化表(少于1024列限制,大多数列将为null或零)。我知道每行限制8060字节,因此我尝试创建具有页面压缩功能的表。下面的代码创建表,插入一行并查询行大小。行大小远低于限制,但是如果我尝试向表中再添加一个小数(26,8)列,操作将失败,并显示错误“创建或更改表't1'失败,因为最小行大小为8074,包括1256”内部开销字节。”。有什么方法可以创建具有这么多列的单个表吗?

drop table t1
GO
create table t1(c1 decimal(26, 8) null)
with (data_compression = page)
GO

declare @i int = 2;
declare @sql varchar(100);
while @i <= 486
begin
    set @sql = 'alter table t1 add c' + convert(varchar, @i) + ' decimal(26, 8) null';
    execute (@sql);
    set @i += 1;
end;
GO


insert into t1(c1) select 0
GO
declare @i int = 2;
declare @sql varchar(100);
while @i <= 486
begin
    set @sql = 'update t1 set c' + convert(varchar, @i) + ' = 0';
    execute (@sql);
    set @i += 1;
end;
GO

select max_record_size_in_bytes from sys.dm_db_index_physical_stats (db_id(), object_id('t1'), NULL, NULL, 'DETAILED')
GO

1
FWIW,我可以将613个DECIMAL(26, 8) NULL字段放入表中,而无需页面压缩或十进制压缩。如果启用vardecimal而不启用页面压缩,那么开销将跃升至1 K以上。您有机会在不使用vardecimal的情况下,每页存储更多字段,具体取决于您的值。
所有行业的乔恩

Answers:


4

您所遇到的限制与页面上存储的数据无关。根据列的数据类型进行计算。这就是为什么您在表中没有任何数据的情况下遇到错误的原因。压缩会使此限制变得更糟。您可以在此处阅读有关间接费用背后的技术细节。

您可以通过使用SPARSE列来解决此问题。这意味着根据插入的内容,插入可能会失败,但是您可以绕过8060字节的限制。以下代码显示您可以创建1023列:

drop table t1
GO
create table t1(c1 decimal(26, 8) null)
GO

declare @i int = 2;
declare @sql varchar(100);
while @i <= 1023
begin
    set @sql = 'alter table t1 add c' + convert(varchar, @i) + ' decimal(26, 8) SPARSE null';
    execute (@sql);
    set @i += 1;
end;
GO

但是,围绕它的所有限制(请阅读链接文章)可能都不适合您的用例。具体来说,只有NULL值(不是0)经过优化才能占用很少的空间。如果您尝试0在单行中插入太多,则会出现错误。这是我尝试插入1023个0值时看到的内容:

消息511,级别16,状态1,行1无法创建大小17402的行,该行大于允许的最大行大小8060。

我想如果您真的很绝望,可以创建列VARCHAR(27)。可变长度列可以移出页面,以便您可以超过表定义中的8060字节限制,但是插入某些值组合将失败。创建表时,SQL Server会警告您:

警告:表“ t1”已创建,但其最大行大小超过了允许的最大8060字节。如果结果行超出大小限制,则对该表的INSERT或UPDATE将失败。

如果采用这种VARCHAR(27)方法,则页面或行压缩可能会有所帮助。这将最大限度地减少双方使用的空间0NULL。随着VARCHAR(27)我能够插入1023倍0的值就好了。


2

@Joe的答案中VARCHAR(27)讨论的技术方面和建议的解决方法之外(使用列),我质疑OP所表达的“ 需要创建[a]宽的非规格化表”,除非存在一些奇怪的技术要求,所有这些列必须在单个表中,我建议/建议根据需要将它们分布在尽可能多的“同级”表中。同级表是以下表:

  • 彼此一对一的关系
  • 都具有完全相同的主键,
  • 只有一个IDENTITY列(对其他列没有FK)
  • 其余的有一个外键(在PK列上),该外键指向具有 IDENTITY

在这里,您将逻辑行拆分为两个或多个物理表。但这本质上就是规范化,以及关系数据库旨在处理的内容。

在这种情况下,由于确实需要将两个INNER JOIN表放在一起(通常但并非总是如此,除非所有SELECT查询都使用所有列,但这种情况通常不会发生),因此您确实会因复制PK而占用一些额外的空间,并增加一些查询的复杂性。或创建显式交易处理INSERTUPDATE一起它们(DELETE可通过处理ON DELETE CASCADE组在FK)。

但是,如果拥有具有正确的本机数据类型的正确数据模型,您将受益匪浅,并且不会在以后产生任何无法预料的后果的诡计多端。即使使用VARCHAR(27)允许它在技术水平上运行,实用上我也不认为将小数存储为字符串符合您/项目的最大利益。

因此,如果由于未意识到不需要在单个容器中物理表示单个逻辑实体而仅“需要”单个表,则不要尝试在可行时将所有这些强制放入单个表中优雅地跨多个表。

下面的示例说明了基本概念:

设定

CREATE TABLE tempdb.dbo.T1
(
  [ID] INT NOT NULL IDENTITY(11, 2) PRIMARY KEY,
  [Col1] VARCHAR(25),
  [Col2] DATETIME NOT NULL DEFAULT (GETDATE())
);

CREATE TABLE tempdb.dbo.T2
(
  [ID] INT NOT NULL PRIMARY KEY
                    FOREIGN KEY REFERENCES tempdb.dbo.T1([ID]) ON DELETE CASCADE,
  [Col3] UNIQUEIDENTIFIER,
  [Col4] BIGINT
);

GO
CREATE PROCEDURE #TestInsert
(
  @Val1 VARCHAR(25),
  @Val4 BIGINT
)
AS
SET NOCOUNT ON;

BEGIN TRY
  BEGIN TRAN;

  DECLARE @InsertedID INT;

  INSERT INTO tempdb.dbo.T1 ([Col1])
  VALUES (@Val1);

  SET @InsertedID = SCOPE_IDENTITY();

  INSERT INTO tempdb.dbo.T2 ([ID], [Col3], [Col4])
  VALUES (@InsertedID, NEWID(), @Val4);

  COMMIT TRAN;
END TRY
BEGIN CATCH
  IF (@@TRANCOUNT > 0)
  BEGIN
    ROLLBACK TRAN;
  END;

  THROW;
END CATCH;

SELECT @InsertedID AS [ID];
GO

测试

EXEC #TestInsert 'aa', 454567678989;

EXEC #TestInsert 'bb', 12312312312234;

SELECT *
FROM   tempdb.dbo.T1
INNER JOIN tempdb.dbo.T2
        ON T2.[ID] = T1.[ID];

返回值:

ID  Col1  Col2                     ID  Col3                                  Col4
11  aa    2017-07-04 10:39:32.660  11  44465676-E8A1-4F38-B5B8-F50C63A947A4  454567678989
13  bb    2017-07-04 10:41:38.180  13  BFE43379-559F-4DAD-880B-B09D7ECA4914  12312312312234
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.