如何将一个NOT NULL列添加到SQL Server中的一个大型表中?
要将NOT NULL列添加到具有多条logging的表中,需要应用DEFAULT约束。 如果表非常大,这个约束会导致整个ALTER TABLE命令花费很长时间才能运行。 这是因为:
假设:
- DEFAULT约束修改现有的logging。 这意味着数据库需要增加每个logging的大小,这会导致它将整个数据页面上的logging转移到其他数据页面,这需要时间。
- DEFAULT更新作为primefaces事务执行。 这意味着交易日志将需要增长,以便必要时可以执行回滚。
- 事务日志logging整个logging。 因此,即使只修改了一个字段,日志所需的空间也将基于整个logging的大小乘以现有logging的数量。 这意味着即使两个表的logging总数相同,向具有小logging的表中添加列也会比向具有大logging的表中添加列要快。
可能的解决scheme:
- 吸起来,等待过程完成。 只要确保将超时时间设置得非常长。 问题在于根据logging数可能需要数小时或数天才能完成。
- 添加列,但允许NULL。 之后,运行UPDATE查询以设置现有行的DEFAULT值。 不要做更新*。 一次更新一批logging,否则最终会出现与解决scheme#1相同的问题。 这种方法的问题是,当你知道这是一个不必要的选项时,你最终会得到一个允许NULL的列。 我相信那里有一些最好的实践文件,说你不应该有允许NULL的列,除非有必要。
- 用相同的模式创build一个新表。 将该列添加到该架构。 从原始表格传输数据。 删除原始表并重命名新表。 我不确定这是否比#1好。
问题:
- 我的假设是否正确?
- 这是我唯一的解决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,但会显着降低其影响。 这是因为每次提交后服务器都会丢弃恢复信息。
你可以:
- 开始交易。
- 在原始表上抓取一个写入锁,以便没有人写入。
- 用新的模式创build一个影子表。
- 传送原始表格中的所有数据。
- 执行sp_rename将旧表重命名。
- 执行sp_rename重新命名新表。
- 最后,你提交交易。
这种方法的优点是你的读者可以在漫长的过程中访问表,并且你可以在后台执行任何模式的变化。
只是用最新的信息来更新它。
在SQL Server 2012中,现在可以在以下情况下执行在线操作
- 仅限企业版
- 缺省值必须是运行时常量
对于第二个需求示例可能是一个字面常量或者一个函数,比如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语句…