如何将一个NOT NULL列添加到SQL Server中的一个大型表中?

要将NOT NULL列添加到具有多条logging的表中,需要应用DEFAULT约束。 如果表非常大,这个约束会导致整个ALTER TABLE命令花费很长时间才能运行。 这是因为:

假设:

  1. DEFAULT约束修改现有的logging。 这意味着数据库需要增加每个logging的大小,这会导致它将整个数据页面上的logging转移到其他数据页面,这需要时间。
  2. DEFAULT更新作为primefaces事务执行。 这意味着交易日志将需要增长,以便必要时可以执行回滚。
  3. 事务日志logging整个logging。 因此,即使只修改了一个字段,日志所需的空间也将基于整个logging的大小乘以现有logging的数量。 这意味着即使两个表的logging总数相同,向具有小logging的表中添加列也会比向具有大logging的表中添加列要快。

可能的解决scheme:

  1. 吸起来,等待过程完成。 只要确保将超时时间设置得非常长。 问题在于根据logging数可能需要数小时或数天才能完成。
  2. 添加列,但允许NULL。 之后,运行UPDATE查询以设置现有行的DEFAULT值。 不要做更新*。 一次更新一批logging,否则最终会出现与解决scheme#1相同的问题。 这种方法的问题是,当你知道这是一个不必要的选项时,你最终会得到一个允许NULL的列。 我相信那里有一些最好的实践文件,说你不应该有允许NULL的列,除非有必要。
  3. 用相同的模式创build一个新表。 将该列添加到该架构。 从原始表格传输数据。 删除原始表并重命名新表。 我不确定这是否比#1好。

问题:

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

我也遇到了这个问题。 而我的解决scheme是在#2。

这里是我的步骤(我正在使用SQL Server 2005):

1)将该列添加到具有默认值的表中:

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

2)用NOCHECK选项添加一个NOT NULL约束。 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条logging。 这允许当时保存大量的数据。 我必须使用“MyColumn IS NULL”,因为我的表没有序列主键。

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

这是我会尝试的:

  • 做一个完整的数据库备份。
  • 添加新列,允许空值 – 不要设置默认值。
  • 设置SIMPLE恢复,一旦每个批处理提交,它将截断tran日志。
  • SQL是: ALTER DATABASE XXX SET RECOVERY SIMPLE
  • 像上面讨论的那样分批运行更新,在每个之后提交。
  • 重置新列不再允许空值。
  • 回到正常的完全恢复。
  • SQL是: ALTER DATABASE XXX SET RECOVERY FULL
  • 再次备份数据库。

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

你可以:

  1. 开始交易。
  2. 在原始表上抓取一个写入锁,以便没有人写入。
  3. 用新的模式创build一个影子表。
  4. 传送原始表格中的所有数据。
  5. 执行sp_rename将旧表重命名。
  6. 执行sp_rename重新命名新表。
  7. 最后,你提交交易。

这种方法的优点是你的读者可以在漫长的过程中访问表,并且你可以在后台执行任何模式的变化。

只是用最新的信息来更新它。

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

  1. 仅限企业版
  2. 缺省值必须是运行时常量

对于第二个需求示例可能是一个字面常量或者一个函数,比如GETDATE() ,对于所有的行都是相同的值。 NEWID()默认NEWID()符合条件,并且仍然会在那里更新所有行。

对于符合条件的SQL Server对其进行求值并将结果作为默认值存储在列元数据中的默认值,这与创build的默认约束无关(甚至可以在不再需要时删除)。 这在sys.system_internals_partition_columns是可见的。 直到下次碰巧更新时,该值才写出到行中。

在这里更详细的信息: 在sql server 2012中的值列添加在线非空

我认为这取决于你正在使用的SQL风格,但是如果你采取了选项2,但在最后的改变表格不是空的默认值?

它会很快,因为它看到所有的值不为空?

如果你想在同一个表中的列,你只需要做。 现在,选项3可能是最好的,因为在这个操作正在进行时,你仍然可以让数据库“活着”。 如果您使用选项1,则在操作发生时表格被locking,然后您确实卡住了。

如果你真的不在乎列是否在表中,那么我认为分段的方法是次好的。 尽pipe如此,我真的试图避免这个问题(因为我不这样做),因为就像Charles Bretana所说的那样,你必须确保find所有更新/插入表的地方并修改它们。 啊!

我有类似的问题,并为您的select#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

我有一个类似的问题,我去修改#3方法。 在我的情况下,数据库处于SIMPLE恢复模式,应该添加列的表没有被任何FK约束引用。

我使用SELECT … INTO语法,而不是创build一个具有相同模式的新表,并复制原始表的内容。

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

SELECT … INTO的日志logging量取决于数据库的恢复模式。 在简单恢复模式或大容量日志恢复模式下,批量操作被最小化logging。 通过最less的日志logging,使用SELECT … INTO语句可以比创build表格更高效,然后使用INSERT语句填充表格。 有关更多信息,请参阅可以最小化logging的操作。

步骤的顺序:

1.将数据从旧表移动到新的列,同时添加默认的新列

  SELECT table.*, cast ('default' as nvarchar(256)) new_column INTO table_copy FROM table 

2.Drop旧桌子

  DROP TABLE table 

重新创build表格

  EXEC sp_rename 'table_copy', 'table' 

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

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

承认这是一个老问题。 最近我的同事告诉我,他能够在1360万行的一张桌子上使用一个alter table语句。 它在SQL Server 2012中完成了一秒钟。我能够确认8M行的表上相同。 在SQL Server的更高版本中有什么改变?

 Alter table mytable add mycolumn char(1) not null default('N'); 

1)将该列添加到具有默认值的表中:

 ALTER TABLE MyTable ADD MyColumn int default 0 

2)在表中增量更新值(与接受的答案相同)。 调整要更新到您的环境的logging数量,以避免阻止其他用户/进程。

 declare @rowcount int = 1 while (@rowcount > 0) begin UPDATE TOP(10000) MyTable SET MyColumn = 0 WHERE MyColumn IS NULL set @rowcount = @@ROWCOUNT end 

3)改变列定义不要求null。 在表格不使用的时候运行以下内容(或安排几分钟的停机时间)。 我已经成功地使用这个数百万条logging的表。

 ALTER TABLE MyTable ALTER COLUMN MyColumn int NOT NULL 

我会使用CURSOR而不是UPDATE。 光标将批量更新所有匹配的logging,通过logging进行logging – 这需要时间,但不锁表。

如果你想避免锁使用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 

我相信有一些语法错误,但我希望这有助于解决您的问题。

祝你好运!

垂直分割表格。 这意味着你将有两个表,具有相同的主键和完全相同的logging数量…一个将是你已经拥有的那个,另一个将只有键,而新的Non-Null列默认值) 。 修改所有的插入,更新和删除代码,使它们保持同步的两个表…如果你想创build一个视图,将这两个表“连接”在一起,创build两个单独的逻辑组合,看起来像一个表客户select语句…