将SQL Server数据库中超过十亿行的列复制到另一列

将SQL Server数据库中超过十亿行的列复制到另一列,sql,sql-server,sql-server-2005,tsql,large-data-volumes,Sql,Sql Server,Sql Server 2005,Tsql,Large Data Volumes,数据库:SQL Server 2005 问题:将值从一列复制到同一表中的另一列(十亿以上) 排 尝试的事情1:更新查询 update test_table set bigid = id 由于缺少事务日志空间,将填充事务日志并回滚 在以下行上尝试了2-a程序 set nocount on set rowcount = 500000 while @rowcount > 0 begin update test_table set bigid = id where bigid is null

数据库:SQL Server 2005

问题:将值从一列复制到同一表中的另一列(十亿以上) 排

尝试的事情1:更新查询

update test_table set bigid = id 
由于缺少事务日志空间,将填充事务日志并回滚

在以下行上尝试了2-a程序

set nocount on
set rowcount = 500000
while @rowcount > 0
begin
 update test_table set bigid = id where bigid is null
 set @rowcount = @@rowcount
 set @rowupdated = @rowsupdated + @rowcount
end
print @rowsupdated
上述过程在进行过程中开始减速

尝试3-为更新创建光标

SQL Server文档中通常不鼓励使用这种方法,这种方法一次更新一行太耗时

是否有一种方法可以加快值从一列复制到另一列。基本上,我正在寻找一些“神奇”的关键字或逻辑,它将允许更新查询按顺序一次翻开50万行


任何提示、指针都将不胜感激。

您可以尝试使用类似
设置行数
的方法并进行批量更新:

SET ROWCOUNT 5000;

UPDATE dbo.test_table 
SET bigid = id 
WHERE bigid IS NULL
GO
然后根据需要重复多次

这样,您就避免了游标和while循环的RBAR(一行接一行)症状,而且也不会不必要地填充事务日志


当然,在两次运行之间,您必须进行备份(尤其是日志备份),以将其大小保持在合理的范围内。

我运行此程序不是为了尝试,但如果您能让它一次更新500k,我认为您正在朝着正确的方向前进

set rowcount 500000
update test_table tt1
set bigid = (SELECT tt2.id FROM test_table tt2 WHERE tt1.id = tt2.id)
where bigid IS NULL
您还可以尝试更改恢复模型,以便不记录事务

ALTER DATABASE db1
SET RECOVERY SIMPLE
GO

update test_table
set bigid = id
GO

ALTER DATABASE db1
SET RECOVERY FULL
GO

这是一次性的吗?如果是这样,只需按范围进行:

set counter = 500000
while @counter < 2000000000 --or whatever your max id
begin
 update test_table set bigid = id where id between (@counter - 500000) and @counter and bigid is null
 set counter = @counter + 500000
end
设置计数器=500000
而@counter<20000000000——或任何您的最大id
开始
更新test_table set bigid=id,其中(@counter-500000)和@counter and bigid之间的id为空
设置计数器=@计数器+500000
结束

如果有的话,第一步是在操作之前删除索引。这可能是导致速度随时间降低的原因

另一个选项,有点跳出框框思考…您能否以这样一种方式表达更新,即可以在select中具体化列值?如果您可以这样做,那么您可以使用SELECT创建一个新表,该表是一个最小日志记录操作(假设您在2005年设置为简单或大容量日志记录的恢复模式)。这将非常快,然后您可以删除旧表,将此表重命名为旧表名并重新创建任何索引

select id, CAST(id as bigint) bigid into test_table_temp from test_table
drop table test_table
exec sp_rename 'test_table_temp', 'test_table'
在以下位置使用TOP:

我附议 更新TOP(X)语句


另外,如果您处于循环中,请添加一些WAITFOR delay或COMMIT-between,以便在需要时允许其他进程使用该表,而不是在所有更新完成之前一直阻塞该表

我猜您正在接近列的人工键上INT数据类型的21亿限制。是的,那很痛苦。在您实际达到该限制并在您尝试修复时关闭生产之前,要比之后更容易修复:)

不管怎样,这里的一些想法会奏效。不过,让我们谈谈速度、效率、索引和日志大小

原木生长 日志最初崩溃是因为它试图同时提交所有2b行。其他帖子中关于“分块”的建议会起作用,但这可能不会完全解决日志问题

如果数据库处于简单模式,您就没事了(日志将在每个批处理后重新使用)。如果数据库处于完全或大容量日志恢复模式,则必须在运行操作期间频繁运行日志备份,以便SQL可以重新使用日志空间。这可能意味着在此期间增加备份频率,或者只是在运行时监视日志使用情况

索引和速度 bigid为null的所有
答案都会随着表格的填充而变慢,因为(可能)新的bigid字段上没有索引。当然,您可以在BIGID上添加一个索引,但我不相信这是正确的答案

密钥(双关语)是我的假设,即原始ID字段可能是主键,或者是聚集索引,或者两者都是。在这种情况下,让我们利用这一事实,改变Jess的想法:

set @counter = 1
while @counter < 2000000000 --or whatever
begin
  update test_table set bigid = id 
  where id between @counter and (@counter + 499999) --BETWEEN is inclusive
  set @counter = @counter + 500000
end
set@counter=1
而@counter<20000000000--或其他
开始
更新测试表集合bigid=id
其中,@counter和(@counter+499999)之间的id--between包含在内
设置@counter=@counter+500000
结束
这应该非常快,因为ID上存在索引


ISNULL检查实际上是不必要的,间隔上的my(-1)也是如此。如果我们在调用之间复制一些行,那没什么大不了的。

不推荐使用连接的子查询。当备选方案(
bigid=id
)如此简单时,在这个巨大的表上做了太多不必要的工作。“针对远程表以及本地和远程分区视图的INSERT、UPDATE和DELETE语句忽略SET ROWCOUNT选项的设置。”来自MSDN。我认为rowcount不能与update命令一起工作,因此需要select命令才能使rowcount工作。我们不处理远程表或分区视图,因此警告不适用。但是是的,
updatetopx
可能比
SET ROWCOUNT
更受欢迎,ROWCOUNT对更新命令有效吗是的,它在SQL 2008之前(包括SQL 2008)工作,在SQL vNext中不工作。但是您是正确的,
updatetop5000…
是SQL 2005及以上版本的首选语法。见爱的RBAR首字母缩略词。“我得把它偷走。”@marc_是的,我把R2放在了“2008”的保护伞下。他们不会在主要编号的修订版之间弃用特性。为什么这在SQLVNEXT(2011/2012/Anwhere)中不起作用???加上vNext将是一个主要的编号版本。。。这意味着,根据你后来的评论,这个“功能”不会被弃用。你会确保它不会被淘汰吗
UPDATE TOP (@row_limit) dbo.test_table
   SET bigid = id 
 WHERE bigid IS NULL
set @counter = 1
while @counter < 2000000000 --or whatever
begin
  update test_table set bigid = id 
  where id between @counter and (@counter + 499999) --BETWEEN is inclusive
  set @counter = @counter + 500000
end