10

数据库: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 文档中通常不鼓励这种方法,并且这种方法一次更新一行,这太耗时了。

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

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

4

7 回答 7

8

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

无论如何,这里的几个想法都会奏效。不过,让我们谈谈速度、效率、索引和日志大小。

对数增长

日志爆炸最初是因为它试图一次提交所有 2b 行。其他帖子中有关“将其分块”的建议将起作用,但这可能无法完全解决日志问题。

如果数据库处于 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) 也不是间隔。如果我们在调用之间复制一些行,那没什么大不了的。

于 2010-09-22T19:47:32.290 回答
5

UPDATE 语句中使用 TOP :

UPDATE TOP (@row_limit) dbo.test_table
   SET bigid = id 
 WHERE bigid IS NULL
于 2010-09-22T19:07:43.200 回答
2

您可以尝试使用类似的东西SET ROWCOUNT并进行批量更新:

SET ROWCOUNT 5000;

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

然后根据需要重复多次。

这样,您就避免了游标和 while 循环的 RBAR(逐行痛苦)症状,而且您不会不必要地填写事务日志。

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

于 2010-09-22T18:54:17.223 回答
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
于 2010-09-22T18:57:04.120 回答
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
于 2010-09-22T18:55:10.157 回答
0

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

另一个选项,有点跳出框框思考......你能以这样一种方式表达更新,你可以在选择中实现列值吗?如果你能做到这一点,那么你可以使用 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'
于 2010-09-22T19:05:01.067 回答
0

我支持 UPDATE TOP(X) 语句

还建议,如果您处于循环中,请在之间添加一些 WAITFOR 延迟或 COMMIT,以允许其他进程有时间在需要时使用该表,而不是永久阻塞,直到所有更新完成

于 2010-09-22T19:23:41.403 回答