29

我正在创建一个包含 1000 列的表。大多数列都是nvarchar类型。表已创建,但有警告

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

表的大部分列中已经有数据(即 99% 的列有数据)。当我尝试在第 310 之后更新任何列时(因为所有开始的 309 列都有一些值),它会给出错误:

无法创建大于允许的最大行大小 8060 的大小为 8061 的行。

我将此数据插入到所有开始的 308 列

“Lorem ipsum dolor sit amet,consectetur adipisicing elit。”

当我使用ntext数据类型时,它允许我更新大约 450 列,但除此之外ntext也不允许我更新。我必须更新至少 700 列。哪个 SQL Server 不允许这样做。我有这样的场景,我无法将表格的某些列移动到另一个表格。

实际上我正在为现有的窗口应用程序工作。这是一个非常大的 Windows 应用程序。

实际上,我尝试在其中插入多达 700 个 nvarchar 列数据的表是在运行时动态创建的。只有在某些情况下,它需要插入 400-600 列。但通常它需要 100 -200 列,我可以轻松处理。

问题是我无法将此表拆分为多个表。因为用这种结构创建的许多表和表的名称都保存在另一个表中,即有超过100个具有这种结构的表并且它们是动态创建的。为了创建表和操作其数据,使用了 4-5 种语言(C#、Java..),并且 WCF、Windows 服务和 Web 服务也涉及。

所以我认为在拆分表格后操作表格及其数据并不容易。如果我拆分表格,则需要进行大量结构更改。

所以请建议我解决这个问题的最佳方法是什么。

我也尝试过使用稀疏列,例如:

Create table ABCD(Id int, Name varchar(100) Sparse, Age int);

我也考虑过ColumnStoreIndex但我的目的没有解决。

稀疏列允许我为一个表创建 3000 列,但它也限制了我的页面大小。

有什么方法可以使用一些临时表或使用任何其他类型的 SQL 服务器对象来实现它?

4

7 回答 7

20

SQL Server 最大列数限制

每个短字符串列的字节数 8,000

每个 GROUP BY 的字节数,ORDER BY 8,060

每行字节数 8,060

每个索引键的列数 16

每个外键的列数16

每个主键的列数16

每个非宽表的列数 1,024

每个宽表的列数 30,000

每个 SELECT 语句的列数 4,096

每个 INSERT 语句的列数 4096

每个 UPDATE 语句的列(宽表) 4096

当您组合每行超过8,060字节的varchar、nvarchar、varbinary、sql_variant或 CLR 用户定义类型列时,请考虑以下事项:

超过 8,060 字节的行大小限制可能会影响性能,因为 SQL Server 仍保持每页 8 KB 的限制。当 varchar、nvarchar、varbinary、sql_variant 或 CLR 用户定义类型列的组合超过此限制时,SQL Server 数据库引擎将具有最大宽度的记录列移动到 ROW_OVERFLOW_DATA 分配单元中的另一个页面,同时保持 24-原始页面上的字节指针。将大记录移动到另一个页面是动态发生的,因为记录基于更新操作而被延长。缩短记录的更新操作可能会导致记录移回 IN_ROW_DATA 分配单元中的原始页面。此外,查询和执行其他选择操作,例如对包含行溢出数据的大型记录进行排序或连接会减慢处理时间,

因此,当您设计具有多个 varchar、nvarchar、varbinary、sql_variant 或 CLR 用户定义类型列的表时,请考虑可能溢出的行的百分比以及可能查询此溢出数据的频率。如果可能对多行行溢出数据进行频繁查询,请考虑对表进行规范化,以便将某些列移动到另一个表。然后可以在异步 JOIN 操作中查询。

  • 对于 varchar、nvarchar、varbinary、sql_variant 和 CLR 用户定义类型列,各个列的长度仍必须在 8,000 字节的限制范围内。只有它们的组合长度可以超过表的 8,060 字节行限制。
  • 其他数据类型列的总和,包括 char 和 nchar 数据,必须在 8,060 字节的行限制内。大对象数据也不受 8,060 字节行的限制。
  • 聚集索引的索引键不能包含在 ROW_OVERFLOW_DATA 分配单元中具有现有数据的 varchar 列。如果在 varchar 列上创建聚集索引,并且现有数据位于 IN_ROW_DATA 分配单元中,则后续插入或更新操作会将数据推送到行外的列将失败。有关分配单元的详细信息,请参阅表和索引组织。
  • 您可以包含包含行溢出数据的列作为非聚集索引的键或非键列。
  • 使用稀疏列的表的记录大小限制为 8,018 字节。当转换后的数据加上已有记录数据超过 8018 字节时,返回 MSSQLSERVER ERROR 576。在稀疏类型和非稀疏类型之间转换列时,数据库引擎会保留当前记录数据的副本。这会暂时使记录所需的存储空间增加一倍。.
  • 要获取有关可能包含行溢出数据的表或索引的信息,请使用 sys.dm_db_index_physical_stats 动态管理函数。

创建具有 n 列和数据类型 Nvarchar 的表

CREATE Proc [dbo].[CreateMaxColTable_Nvarchar500]
(@TableName nvarchar(100),@NumofCols int)
AS
BEGIN

DECLARE @i INT
DECLARE @MAX INT
DECLARE @SQL VARCHAR(MAX)
DECLARE @j VARCHAR(10)
DECLARE @len int
SELECT @i=1
SELECT @MAX=@NumofCols
SET @SQL='CREATE TABLE ' + @TableName + '('

WHILE @i<=@MAX

BEGIN
select @j= cast(@i as varchar)
SELECT @SQL= @SQL+'X'+@j  +' NVARCHAR(500) , '
SET @i = @i + 1
END
select @len=len(@SQL)

select  @SQL = substring(@SQL,0,@len-1)


SELECT @SQL= @SQL+ ' )'

exec (@SQL)

END

有关更多信息,您可以访问以下链接:

http://msdn.microsoft.com/en-us/library/ms186981%28SQL.105%29.aspx?PHPSESSID=tn8k5p1s508cop8gr43e1f34d2

http://technet.microsoft.com/en-us/library/ms143432.aspx

但是请你能告诉这个场景为什么你需要一个有这么多列的表吗?我认为您应该考虑重新设计数据库。

于 2013-08-07T19:38:20.157 回答
12

这根本不可能。请参阅存储引擎内部:记录剖析

假设你的桌子是这样的。

CREATE TABLE T1(
    col_1 varchar(8000) NULL,
    col_2 varchar(8000) NULL,
    /*....*/
    col_999 varchar(8000) NULL,
    col_1000 varchar(8000) NULL
) 

然后,即使是包含所有NULL值的行也将使用以下存储。

  • 1 字节状态位 A
  • 1 字节状态位 B
  • 2 字节列计数偏移
  • 125 字节NULL_BITMAPbit每列 1,000 列)

因此,保证 129 个字节已经用完(剩下 7,931 个)。

如果任何列的值既不是空字符串也不是NULL空字符串,那么您还需要空间

  • 2 字节可变长度列数(剩余 7,929)。
  • 列偏移数组的 2 - 2000 字节之间的任意位置。
  • 数据本身。

列偏移数组每个可变长度列消耗 2 个字节,除非该列和所有后续列的长度也为零。因此更新col_1000将强制使用整个 2000 个字节,而更新 col_1将只使用 2 个字节。

因此,您可以用 5 个字节的数据填充每列,并且当考虑到列偏移数组中的每个 2 个字节时,这将加起来 7,000 个字节,这在剩余的 7,929 个字节内。

但是,您要存储的数据是 102 字节(51 个nvarchar字符),因此可以使用 24 字节指针将其存储在行外,指向行中剩余的实际数据。

FLOOR(7929/(24 + 2)) = 304

因此,最好的情况是您可以存储 304 列的此长度数据,也就是说,如果您从col_1col_2、进行更新...。如果col_1000包含数据,则计算为

FLOOR(5929/24) = 247

因为NTEXT计算是相似的,除了它可以使用一个 16 字节的指针,这将允许您将数据压缩到几个额外的列中

FLOOR(7929/(16 + 2)) = 440

需要针对表中的任何对象跟踪所有这些行外指针SELECT可能对性能非常不利。

测试这个的脚本

DROP TABLE T1

/* Create table with 1000 columns*/
DECLARE @CreateTableScript nvarchar(max) = 'CREATE TABLE T1('

SELECT @CreateTableScript += 'col_' + LTRIM(number) + ' VARCHAR(8000),'
FROM master..spt_values
WHERE type='P' AND number BETWEEN 1 AND 1000
ORDER BY number

SELECT @CreateTableScript += ')'

EXEC(@CreateTableScript)

/* Insert single row with all NULL*/
INSERT INTO T1 DEFAULT VALUES


/*Updating first 304 cols succeed. Change to 305 and it fails*/
DECLARE @UpdateTableScript nvarchar(max) = 'UPDATE T1 SET  '

SELECT @UpdateTableScript += 'col_' + LTRIM(number) + ' = REPLICATE(1,1000),'
FROM master..spt_values
WHERE type='P' AND number BETWEEN 1 AND 304
ORDER BY number

SET @UpdateTableScript = LEFT(@UpdateTableScript,LEN(@UpdateTableScript)-1)
EXEC(@UpdateTableScript)
于 2012-12-28T16:16:56.427 回答
9

拥有 1.000 列的表告诉您数据库设计中有一些非常错误的地方。

我继承了一个项目,其中一张表有超过 500 列,一年多后我仍然无法显着减少它,因为我将不得不重做 90% 的应用程序。

因此,在为时已晚之前重新设计您的数据库。

于 2012-12-28T14:36:04.917 回答
2

每个“非宽”表的最大列数:1,024 每个“宽”表的最大列数:30,000

尽管您需要每个表的这个数字到底是什么情况?强烈建议对表进行多次垂直分区,以获得更好的性能和更轻松的开发。

于 2012-12-28T14:35:01.833 回答
2

创建具有 n 列和数据类型 Nvarchar 的表

CREATE Proc [dbo].[CreateMaxColTable_Nvarchar500]
(@TableName nvarchar(100),@NumofCols int)
AS
BEGIN

DECLARE @i INT
DECLARE @MAX INT
DECLARE @SQL VARCHAR(MAX)
DECLARE @j VARCHAR(10)
DECLARE @len int
SELECT @i=1
SELECT @MAX=@NumofCols
SET @SQL='CREATE TABLE ' + @TableName + '('

WHILE @i<=@MAX

BEGIN
select @j= cast(@i as varchar)
SELECT @SQL= @SQL+'A'+@j  +' NVARCHAR(500) , '
SET @i = @i + 1
END
select @len=len(@SQL)

select  @SQL = substring(@SQL,0,@len-1)


SELECT @SQL= @SQL+ ' )'

exec (@SQL)

END
于 2013-02-06T06:00:11.363 回答
0

请检查您是否真的需要对所有列使用 nvarchar,nvarchar 占用的字节数是 varchar 的两倍,因此在可能的情况下将列转换为 varchar 可以减少行数据大小,并可能帮助您克服此错误。

于 2021-02-28T06:11:10.653 回答
-3

我们有一个应用程序,它为贷款申请捕获 5000 个字段。所有字段都依赖于一个主键loanid。我们可以将表拆分为多个,但字段也是动态的。管理员还具有创建更多字段的功能。所以一切都是动态的。他们唯一的好处是贷款和字段之间的一对一关系。

所以,最后我们选择了 XML 解决方案。整个数据存储在一个 xml 文档中。最大的灵活性,但难以查询和报告。

于 2015-07-27T22:57:42.550 回答