数据库: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亿行。
任何提示,指示都将非常感激。
答案 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)
另外建议,如果你在一个循环中,添加一些WAITFOR延迟或COMMIT之间,以允许其他进程一段时间使用该表(如果需要)与永久阻塞直到所有更新完成