如何在SQL Server中的大表中添加NOT NULL列?

时间:2008-11-13 19:28:17

标签: sql-server

要将NOT NULL列添加到包含许多记录的表中,需要应用DEFAULT约束。如果表非常大,则此约束会导致整个ALTER TABLE命令运行很长时间。这是因为:

假设:

  1. DEFAULT约束修改现有记录。这意味着db需要增加每条记录的大小,这会导致它将完整数据页上的记录转移到其他数据页,这需要时间。
  2. DEFAULT更新作为原子事务执行。这意味着需要增加事务日志,以便在必要时执行回滚。
  3. 事务日志会跟踪整个记录。因此,即使只修改了一个字段,日志所需的空间也将基于整个记录的大小乘以现有记录的数量。这意味着向具有小记录的表添加列将比向具有大记录的表添加列更快,即使两个表的记录总数相同也是如此。
  4. 可能的解决方案:

    1. 吸收它并等待该过程完成。只需确保将超时时间设置得很长。这样做的问题是,根据记录数量,可能需要数小时或数天才能完成。
    2. 添加列但允许NULL。然后,运行UPDATE查询以设置现有行的DEFAULT值。不要做UPDATE *。一次更新批量记录,否则您将遇到与解决方案#1相同的问题。这种方法的问题在于,当您知道这是一个不必要的选项时,最终会得到一个允许NULL的列。我相信有一些最佳实践文档说明你不应该有允许NULL的列,除非有必要。
    3. 使用相同的架构创建新表。将列添加到该架构。从原始表传输数据。删除原始表并重命名新表。我不确定这比#1更好。
    4. 问题:

      1. 我的假设是否正确?
      2. 这些是我唯一的解决方案吗?如果是这样,哪一个是最好的?我不是,我还能做什么?

12 个答案:

答案 0 :(得分:59)

我也为我的工作遇到了这个问题。我的解决方案是#2。

以下是我的步骤(我使用的是SQL Server 2005):

1)使用默认值将列添加到表中:

ALTER TABLE MyTable ADD MyColumn varchar(40) DEFAULT('')

2)使用NOT NULL选项添加NOCHECK约束。 NOCHECK不对现有值强制执行:

ALTER TABLE MyTable WITH NOCHECK
ADD CONSTRAINT MyColumn_NOTNULL CHECK (MyColumn IS NOT NULL)

3)在表格中逐步更新值:

GO
UPDATE TOP(3000) MyTable SET MyColumn = '' WHERE MyColumn IS NULL
GO 1000
  • 更新语句只会更新最多3000条记录。这允许当时保存一大块数据。我必须使用“MyColumn IS NULL”,因为我的表没有序列主键。

  • GO 1000将执行前一个语句1000次。这将更新300万条记录,如果您需要更多,只需增加此数字。它将继续执行,直到SQL Server为UPDATE语句返回0条记录。

答案 1 :(得分:3)

以下是我要尝试的内容:

  • 执行数据库的完整备份。
  • 添加新列,允许空值 - 不设置默认值。
  • 设置SIMPLE恢复,一旦提交每个批次,就会截断tran日志。
  • SQL是: ALTER DATABASE XXX SET RECVVERY SIMPLE
  • 如上所述,分批运行更新,在每个更新后提交。
  • 将新列重置为不再允许空值。
  • 恢复正常的完全恢复。
  • SQL是: ALTER DATABASE XXX SET RECOVERY FULL
  • 再次备份数据库。

使用SIMPLE恢复模型不会停止记录,但会显着降低其影响。这是因为服务器在每次提交后都会丢弃恢复信息。

答案 2 :(得分:2)

你可以:

  1. 开始交易。
  2. 在原始表上获取写锁定,因此没有人写入它。
  3. 使用新架构创建影子表。
  4. 传输原始表格中的所有数据。
  5. 执行sp_rename以重命名旧表。
  6. 执行sp_rename以重命名新表。
  7. 最后,您提交了交易。
  8. 这种方法的优点是您的读者可以在漫长的过程中访问该表,并且您可以在后台执行任何类型的模式更改。

答案 3 :(得分:2)

只是用最新信息更新它。

在SQL Server 2012中,现在可以在以下情况下将其作为在线操作执行

  1. 仅限企业版
  2. 默认值必须是运行时常量
  3. 对于第二个要求,示例可能是文字常量或诸如GETDATE()之类的函数,它会为所有行计算相同的值。默认值为NEWID() 符合条件,并且最终仍会更新所有行。

    对于限定SQL Server的默认值对它们进行评估并将结果存储为列元数据中的默认值,因此这与创建的默认约束无关(如果不再需要,甚至可以删除它)。这可以在sys.system_internals_partition_columns中查看。在下次碰巧更新之前,该值不会被写入行。

    有关此内容的详细信息:online non-null with values column add in sql server 2012

答案 4 :(得分:0)

我认为这取决于您正在使用的SQL风格,但是如果您使用选项2,但是在最后的alter table表中使用默认值不为null会怎么样?

是否会很快,因为它看到所有值都不为空?

答案 5 :(得分:0)

如果您希望列在同一个表中,您只需要这样做。现在,选项3可能是最好的,因为在此操作进行时您仍然可以使数据库“活动”。如果你使用选项1,那么在操作发生时表被锁定,然后你真的被卡住了。

如果你真的不在乎列是否在表中,那么我认为分段方法是次佳的。虽然,我真的试图避免这种情况(我不这样做),因为就像Charles Bretana所说的那样,你必须确保并找到所有更新/插入该表并修改它们的地方。啊!

答案 6 :(得分:0)

我遇到了类似的问题,并选择了#2。 这种方式需要20分钟,而另一方面需要32小时!巨大的差异,谢谢你的提示。 我写了一篇关于它的完整博客文章,但这里是重要的sql:

Alter table MyTable
Add MyNewColumn char(10) null default '?';
go

update MyTable set MyNewColumn='?' where MyPrimaryKey between 0 and 1000000
go
update MyTable set MyNewColumn='?' where MyPrimaryKey between 1000000 and 2000000
go
update MyTable set MyNewColumn='?' where MyPrimaryKey between 2000000 and 3000000
go
..etc..

Alter table MyTable
Alter column MyNewColumn char(10) not null;

如果您有兴趣,请参阅博客文章: http://splinter.com.au/adding-a-column-to-a-massive-sql-server-table

答案 7 :(得分:0)

我有类似的问题,我采用了改进的#3方法。在我的情况下,数据库处于SIMPLE恢复模式,并且应该添加列的表未被任何FK约束引用。

我使用 SELECT ... INTO 语法,而不是使用相同的架构创建新表并复制原始表的内容。

根据微软(http://technet.microsoft.com/en-us/library/ms188029(v=sql.105).aspx

  

SELECT ... INTO的日志记录量取决于恢复模型   对数据库有效。在简单的恢复模式或   批量记录恢复模型,批量操作记录最少。同   最小的日志记录,使用SELECT ... INTO语句可以更多   比创建一个表然后用一个表填充表有效   INSERT语句。有关更多信息,请参阅可以执行的操作   记录最少。

步骤顺序:

1.使用默认

添加新列时,将旧表中的数据移动到新表
 SELECT  table.*,   cast (‘default’ as nvarchar(256)) new_column
 INTO    table_copy 
 FROM    table

2.Drop旧表

 DROP TABLE  table

3.重新命名新创建的表

 EXEC sp_rename 'table_copy',  ‘table’

4.在新表上创建必要的约束和索引

在我的情况下,该表有超过1亿行,这种方法比方法#2完成得更快,而且日志空间增长很少。

答案 8 :(得分:0)

承认这是一个老问题。我的同事最近告诉我,他能够在一个包含13.6M行的表上的单个alter table语句中执行此操作。它在SQL Server 2012中完成了一秒钟。我能够在具有8M行的表上确认相同的内容。在SQL Server的更高版本中有什么变化?

dd

答案 9 :(得分:0)

1)使用默认值将列添加到表中:

<!DOCTYPE html>
<html>
<head>
<style>
div {
    width: 100px;
    height: 100px;
    background-color: red;
    -webkit-animation-name: example; /* Safari 4.0 - 8.0 */
    -webkit-animation-duration: 4s; /* Safari 4.0 - 8.0 */
    animation-name: example;
    animation-duration: 4s;
}

/* Safari 4.0 - 8.0 */
@-webkit-keyframes example {
    from {background-color: red;}
    to {background-color: yellow;}
}

/* Standard syntax */
@keyframes example {
    from {background-color: red;}
    to {background-color: yellow;}
}
</style>
</head>
<body>

<p><b>Note:</b> This example does not work in Internet Explorer 9 and earlier versions.</p>

<div></div>

<p><b>Note:</b> When an animation is finished, it changes back to its original style.</p>

</body>
</html>

2)在表格中逐步更新值(与接受的答案效果相同)。调整要更新到您的环境的记录数,以避免阻止其他用户/进程。

ALTER TABLE MyTable ADD MyColumn int default 0

3)将列定义更改为require not null。在表未使用时(或安排几分钟的停机时间)运行以下命令。我已经成功地将它用于包含数百万条记录的表格。

declare @rowcount int = 1

while (@rowcount > 0)
begin           

    UPDATE TOP(10000) MyTable SET MyColumn = 0 WHERE MyColumn IS NULL       
    set @rowcount = @@ROWCOUNT

end

答案 10 :(得分:-1)

我会使用CURSOR而不是UPDATE。光标将批量更新所有匹配的记录,按记录记录 - 需要时间但不是锁表。

如果你想避免锁定,请使用WAIT。

我也不确定,DEFAULT约束会更改现有行。 可能NOT NULL约束与DEFAULT一起使用导致作者描述的情况。

如果更改,请将其添加到最后 所以伪代码看起来像:

-- without NOT NULL constrain -- we will add it in the end
ALTER TABLE table ADD new_column INT DEFAULT 0

DECLARE fillNullColumn CURSOR LOCAL FAST_FORWARD
    SELECT 
        key
    FROM
        table WITH (NOLOCK)
    WHERE
        new_column IS NULL

OPEN fillNullColumn

DECLARE 
    @key INT

FETCH NEXT FROM fillNullColumn INTO @key

WHILE @@FETCH_STATUS = 0 BEGIN
     UPDATE
         table WITH (ROWLOCK)
     SET
         new_column = 0 -- default value
     WHERE
         key = @key

     WAIT 00:00:05 --wait 5 seconds, keep in mind it causes updating only 12 rows per minute

     FETCH NEXT FROM fillNullColumn INTO @key
END

CLOSE fillNullColumn
DEALLOCATE fillNullColumn

ALTER TABLE table ALTER COLUMN new_column ADD CONSTRAIN xxx

我确信存在一些语法错误,但我希望如此 帮助解决您的问题。

祝你好运!

答案 11 :(得分:-3)

垂直分割表格。这意味着你将有两个表,具有相同的主键和完全相同的记录数...一个将是你已经拥有的,另一个将只有密钥,以及新的非空列(带有默认值) 。 修改所有插入,更新和删除代码,以便它们保持两个表同步...如果需要,您可以创建一个视图,将两个表“连接”在一起,以创建两个表的单个逻辑组合,看起来像一个客户选择表语句...