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

时间:2010-09-22 18:49:54

标签: sql sql-server sql-server-2005 tsql large-data-volumes

数据库:SQL Server 2005

问题:将值从一列复制到同一个表中的另一列,使用十亿+ 行。

test_table (int id, bigint bigid)

事情尝试1:更新查询

update test_table set bigid = id 

填写事务日志并由于缺少事务日志空间而回滚。

尝试2 - 以下行的程序

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亿行。

任何提示,指示都将非常感激。

7 个答案:

答案 0 :(得分:5)

UPDATE statement中使用TOP:

UPDATE TOP (@row_limit) dbo.test_table
   SET bigid = id 
 WHERE bigid IS NULL

答案 1 :(得分:4)

我猜你正在接近列的人工密钥上的INT数据类型的21亿个限制。是的,这很痛苦。事实上比在实际达到限制之后更容易解决,并且在您尝试修复它时关闭生产:)

无论如何,这里的一些想法都会奏效。我们来谈谈速度,效率,索引和日志大小。

日志增长

日志最初爆炸,因为它试图一次提交所有2b行。其他帖子中“chunking it up”的建议可行,可能无法完全解决日志问题。

如果数据库处于SIMPLE模式,您就可以了(日志将在每批后重新使用)。如果数据库处于FULL或BULK_LOGGED恢复模式,则必须在运行操作期间频繁运行日志备份,以便SQL可以重用日志空间。这可能意味着在此期间增加备份频率,或者仅在运行时监视日志使用情况。

索引和速度

所有where bigid is 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

这应该非常快,因为ID上存在现有索引。

无论如何,ISNULL检查确实没有必要,也不是我的(-1)。如果我们在调用之间复制一些行,那不是什么大问题。

答案 2 :(得分:2)

您可以尝试使用SET ROWCOUNT之类的内容进行批量更新:

SET ROWCOUNT 5000;

UPDATE dbo.test_table 
SET bigid = id 
WHERE bigid IS NULL
GO

然后根据需要多次重复此操作。

通过这种方式,您可以避免游标和while循环的RBAR(逐行 - 痛苦行)症状,但是,您不会不必要地填写事务日志。

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

答案 3 :(得分:2)

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

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

答案 4 :(得分:0)

我没有试过这个尝试,但如果你能让它一次更新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

答案 5 :(得分:0)

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

另一个选项,在盒子外面思考一下......你能用这样的方式表达更新吗?你可以在select中实现列值吗?如果你可以这样做,那么你可以使用SELECT INTO创建相当于新表的内容,这是一个最小化日志操作(假设在2005年你被设置为SIMPLE或BULK LOGGED的恢复模型)。这将非常快,然后您可以删除旧表,将此表重命名为旧表名并重新创建任何索引。

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'

答案 6 :(得分:0)

我是第二个 更新TOP(X)声明

另外建议,如果你在一个循环中,添加一些WAITFOR延迟或COMMIT之间,以允许其他进程一段时间使用该表(如果需要)与永久阻塞直到所有更新完成