更新1.2亿条记录的最快方式

时间:2010-09-14 17:08:43

标签: sql sql-server sql-server-2005

我需要在一个1.2亿记录表中初始化一个值为-1的新字段。

Update table
       set int_field = -1;

我让它在取消之前运行了5个小时。

我尝试运行它,事务级别设置为读取未提交且具有相同的结果。

Recovery Model = Simple.
MS SQL Server 2005

有关更快完成此任务的任何建议吗?

11 个答案:

答案 0 :(得分:34)

更新120M记录表的唯一合理方法是使用SELECT语句填充第二个表。这样做时你必须小心。说明如下。


简单案例

对于没有聚集索引的表,在w / out并发DML的时间内:

  • SELECT *, new_col = 1 INTO clone.BaseTable FROM dbo.BaseTable
  • 在新表上重新创建索引,约束等
  • 切换旧的和新的w / ALTER SCHEMA ... TRANSFER。
  • 放弃旧桌

如果无法创建克隆架构,则同一架构中的其他表名称将起作用。切记在切换后重命名所有约束和触发器(如果适用)。


非简单案例

首先,在不同的架构下重新创建具有相同名称的BaseTable,例如clone.BaseTable。使用单独的模式将在以后简化重命​​名过程。

  • 包含聚集索引(如果适用)。请记住,主键和唯一约束可能是聚类的,但不一定如此。
  • 包含标识列和计算列(如果适用)。
  • 包括您的新INT列,无论它位于何处。
  • 不包含以下任何内容:
    • 触发器
    • 外键约束
    • 非聚集索引/主键/唯一约束
    • 检查约束或默认约束。默认值没有多大区别,但我们正在努力保持 事情很少。

然后,测试你的插件w / 1000行:

-- assuming an IDENTITY column in BaseTable
SET IDENTITY_INSERT clone.BaseTable ON
GO
INSERT clone.BaseTable WITH (TABLOCK) (Col1, Col2, Col3)
SELECT TOP 1000 Col1, Col2, Col3 = -1
FROM dbo.BaseTable
GO
SET IDENTITY_INSERT clone.BaseTable OFF

检查结果。如果一切按顺序显示:

  • 截断克隆表
  • 确保数据库处于批量记录或简单恢复模式
  • 执行完整插入。

这需要一段时间,但不会像更新那么长。完成后,检查克隆表中的数据以确保一切正确。

然后,重新创建所有非群集主键/唯一约束/索引和外键约束(按此顺序)。如果适用,重新创建默认值并检查约束。重新创建所有触发器。在单独的批处理中重新创建每个约束,索引或触发器。例如:

ALTER TABLE clone.BaseTable ADD CONSTRAINT UQ_BaseTable UNIQUE (Col2)
GO
-- next constraint/index/trigger definition here

最后,将dbo.BaseTable移至备用架构,将clone.BaseTable移至dbo架构(或您的表应存在的任何位置)。

-- -- perform first true-up operation here, if necessary
-- EXEC clone.BaseTable_TrueUp
-- GO
-- -- create a backup schema, if necessary
-- CREATE SCHEMA backup_20100914
-- GO
BEGIN TRY
  BEGIN TRANSACTION
  ALTER SCHEMA backup_20100914 TRANSFER dbo.BaseTable
  -- -- perform second true-up operation here, if necessary
  -- EXEC clone.BaseTable_TrueUp
  ALTER SCHEMA dbo TRANSFER clone.BaseTable
  COMMIT TRANSACTION
END TRY
BEGIN CATCH
  SELECT ERROR_MESSAGE() -- add more info here if necessary
  ROLLBACK TRANSACTION
END CATCH
GO

如果您需要释放磁盘空间,此时您可能会放弃原始表格,但保持一段时间可能是谨慎的。

毋庸置疑,理想情况下,这是离线操作。如果您在执行此操作时有人修改数据,则必须使用架构开关执行校正操作。我建议在dbo.BaseTable上创建一个触发器,将所有DML记录到一个单独的表中。在开始插入之前启用此触发器。然后,在执行模式传输的同一事务中,使用日志表执行校正。首先在数据子集上进行测试! Deltas很容易搞砸。

答案 1 :(得分:13)

如果您有磁盘空间,可以使用SELECT INTO并创建一个新表。它的记录最少,因此速度会快得多

select t.*, int_field = CAST(-1 as int)
into mytable_new 
from mytable t

-- create your indexes and constraints

GO

exec sp_rename mytable, mytable_old
exec sp_rename mytable_new, mytable

drop table mytable_old

答案 2 :(得分:9)

我把任务分成更小的单位。为您的表测试不同的批处理大小间隔,直到找到最佳执行的间隔。这是我过去使用过的一个示例。

declare @counter int 
declare @numOfRecords int
declare @batchsize int

set @numOfRecords = (SELECT COUNT(*) AS NumberOfRecords FROM <TABLE> with(nolock))
set @counter = 0 
set @batchsize = 2500

set rowcount @batchsize
while @counter < (@numOfRecords/@batchsize) +1
begin 
set @counter = @counter + 1 
Update table set int_field = -1 where int_field <> -1;
end 
set rowcount 0

答案 3 :(得分:4)

如果您的int_field已编制索引,请在运行更新之前删除索引。然后再次创建索引......

对于1.2亿人来说,5个小时似乎很多。

答案 4 :(得分:3)

set rowcount 1000000
Update table set int_field = -1 where int_field<>-1

了解根据需要采取,调整和重复的速度

答案 5 :(得分:2)

我首先尝试的是什么 在更新之前首先删除所有约束,索引,触发器和全文索引。

如果上述情况不够好,我的下一步行动将是 创建一个包含1200万条记录的CSV文件,并使用bcp批量导入它。

最后,我创建了一个新的堆表(意思是没有主键的表),在不同的文件组上没有索引,用-1填充它。对旧表进行分区,并使用“switch”添加新分区。

答案 6 :(得分:2)

添加新列(“初始化新字段”)并为每个现有行设置单个值时,我使用以下策略:

ALTER TABLE MyTable
 add NewColumn  int  not null
  constraint MyTable_TemporaryDefault
   default -1

ALTER TABLE MyTable
 drop constraint MyTable_TemporaryDefault

如果列可以为空并且您没有包含“声明”约束,则对于所有行,该列将设置为null。

答案 7 :(得分:2)

declare @cnt bigint
set @cnt = 1

while @cnt*100<10000000 
 begin

UPDATE top(100) [Imp].[dbo].[tablename]
   SET [col1] = xxxx       
 WHERE[col2] is null  

  print '@cnt: '+convert(varchar,@cnt)
  set @cnt=@cnt+1
  end

答案 8 :(得分:1)

听起来像索引问题,就像Pabla Santa Cruz提到的那样。由于您的更新不是有条件的,您可以删除列并使用DEFAULT值重新添加它。

答案 9 :(得分:0)

一般来说,建议如下:

  1. 删除或仅禁用表格中的所有INDEXES,TRIGGERS,CONSTRAINTS;
  2. 更频繁地执行COMMIT(例如,在每1000条已更新的记录之后);
  3. 使用select ... into。
  4. 但在特殊情况下,您应该选择最合适的解决方案或它们的组合。

    还要记住,某些时候索引可能是有用的,例如当您按某种条件执行非索引列的更新时。

答案 10 :(得分:0)

如果表中有一个可以迭代的索引,我会将update top(10000)语句放在while循环中移动数据。这将使事务日志保持苗条,并且不会对磁盘​​系统产生如此巨大的影响。另外,我建议使用maxdop选项(将其设置为接近1)。