将具有默认值的列添加到大表

FaN*_*NIX 9 sql sql-server performance

我有一张有 4000 万条记录的表。我需要向该表添加一个新的 INT NOT NULL 列,默认值 = 0

使用以下内容添加此列时:

ALTER TABLE myTable ADD NewColumnID int NOT NULL CONSTRAINT DF_Constraint DEFAULT 0
Run Code Online (Sandbox Code Playgroud)

它将所有记录的 NewColumnID 设置为 0。在我们有 4000 万条记录的 prod 表上运行这个查询时,这需要很长时间吗?因为我知道执行以下操作需要很长时间:

UPDATE myTable SET NewColumnID = 0
Run Code Online (Sandbox Code Playgroud)

更新:2020 年 1 月 5 日:

自从我上次登录我的堆栈溢出帐户以来已经有一段时间了。我注意到我在 2013 年发布的这个特定问题。我收到了一些针对这个问题的糟糕代表,现在我明白了原因。我不得不通读好几遍才能理解我到底在问什么以及答案是如何适用的。看到它已被查看超过 6000 次,也许值得(7 年后,抱歉)提供更多背景信息。

请允许我澄清这个问题:

我在一家银行软件提供商工作。我们在世界各地有各种各样的客户,并且正在对我们的软件进行大规模更新,这需要将一个新列添加到我们软件使用的现有表中。根据银行的规模,这张特殊的桌子通常很大。要求是在第一次添加列时,将特定 ID 分配给所有现有记录,之后表中的所有新条目都将恢复为值“0”。

所以......在测试阶段,我们注意到在我们的升级脚本中包含以下内容需要将近一个小时来处理 40m 记录:

ALTER TABLE myTable ADD NewColumnID int NOT NULL CONSTRAINT DF_Constraint DEFAULT 0
UPDATE myTable SET NewColumnID = 50
Run Code Online (Sandbox Code Playgroud)

上面的示例将添加新列,然后使用 NewColumnID = 50 更新所有现有记录。这在运行它的硬件上花费了将近一个小时。我明白这会因客户的基础设施而有很大差异。

提出这个问题的原因是想看看是否有更快的方法来完成上述工作。

请允许我澄清答案:

我完全理解为什么我的回答没有意义,但希望以下解释会有所帮助:

您不是添加列然后运行更新查询,而是通过创建一个具有默认值的 CONSTRAINT 来分配您希望所有现有记录继承的值,该默认值是您想要更新它的值。列的创建将导致此值被自动插入:

ALTER TABLE myTable ADD CompanyID int NOT NULL CONSTRAINT DF_Constraint DEFAULT 1 (takes about 1min to complete)
Run Code Online (Sandbox Code Playgroud)

它本质上是“一石二鸟”。这个查询完全在大约 1 分钟内完成,大约一个小时(在同一台服务器上执行)。既然需要为所有现有记录添加一个默认 id = x(每个客户端都不同)的新列,那么DEFAULT 0约束被恢复,以便所有新插入的记录在没有传递值的情况下将假定值为 0。因此引用:

然后只需将默认值设置回 0。现在表中所有记录的 CompanyID = 1。繁荣!

抱歉……这是 7 年前的事了,现在看来这一切都很愚蠢:) 但谁知道呢,也许这可以帮助其他需要创造性黑客的愚蠢要求的人:)!

Aar*_*and 5

主要问题是这需要写入每一行,这些行被大量记录为单个事务。最小化对日志的影响的一种方法(如果您的日志文件上没有愚蠢的 10% 自动增长设置,这种方法效果最好)是尽可能地分解工作:

  1. 添加一个可空列:

    ALTER TABLE dbo.myTable 
     ADD NewColumnID INT CONSTRAINT DF_Constraint DEFAULT 0;
    
    Run Code Online (Sandbox Code Playgroud)
  2. 批量更新行,一次说 10K 行(这将最大限度地减少日志影响 - 请参阅此博客文章了解背景):

    BEGIN TRANSACTION;
    SELECT 1;
    WHILE @@ROWCOUNT > 0
    BEGIN
      COMMIT TRANSACTION;
      BEGIN TRANSACTION;
    
      UPDATE TOP (10000) dbo.myTable SET NewColumnID = 0;
    END
    COMMIT TRANSACTION;
    
    Run Code Online (Sandbox Code Playgroud)
  3. 添加检查约束(有关更多详细信息,请参阅这些答案):

    ALTER TABLE dbo.myTable WITH CHECK
      ADD CONSTRAINT NewCol_Not_Null
      CHECK (NewColumnID IS NOT NULL); 
    
    Run Code Online (Sandbox Code Playgroud)

    您可以通过使用NOCHECK此处节省一些时间,但正如 Martin 在他的回答中所解释的那样,这是一次性的节省,从长远来看可能会让您头疼。

这已在上一个问题中解决,但那里接受的答案使用 NOCHECK,没有任何关于不受信任的约束如何影响执行计划的免责声明。


FaN*_*NIX -1

感谢亚伦提供的详细方法,但我做了一个快速测试,简单的方法是执行以下操作:

一些背景。我正在将 CompanyID 添加到现有的大型表中。ID 指的是记录所属的公司。默认值为 0。但由于这是进入现有客户产品数据库,因此他们的公司 ID 为 1。我们为所有客户提供通用升级脚本,结果是针对该特定客户对此脚本进行轻微修改,从而产生显着的性能改进。

代替:

ALTER TABLE myTable ADD CompanyID int NOT NULL CONSTRAINT DF_Constraint DEFAULT 0 (takes about 1min to complete)
UPDATE myTable SET CompanyID = 1 (will take over an hour)
Run Code Online (Sandbox Code Playgroud)

我只是这样做:

ALTER TABLE myTable ADD CompanyID int NOT NULL CONSTRAINT DF_Constraint DEFAULT 1 (takes about 1min to complete)
Run Code Online (Sandbox Code Playgroud)

然后只需将默认值设置回 0。现在表中所有记录的 CompanyID = 1。繁荣!

  • 添加一列并将所有 4000 万行设置为 1 花了一分钟?您能解释一下为什么您认为将默认值设置为 1 比将默认值设置为 0 快得多吗?我很困惑。答案谈到设置一个默认值并将所有数据更新为不同的值,而问题根本没有提到值 1。 (2认同)