如何更新在包含超过2.5亿行的表中创建的2个新列

时间:2013-06-21 21:11:20

标签: sql-server

我必须将2个新列col1 char(1) NULLcol2 char(1) NULL添加到行数超过2.5亿的表中。对于现有的2.5亿行,我必须使用值1更新两列。

然后我的SSIS包将每天以增量顺序更新表。 SSIS包将使用来自源表的任何内容填充这两列。

如何快速完成此操作,因为我必须更新250M行?

谢谢, 男人

1 个答案:

答案 0 :(得分:7)

您没有说出您正在使用的SQL Server版本。从SQL Server 2012开始,添加带有默认值的新NOT NULL列在大多数情况下为instantaneous:仅更改表元数据,并且不更新任何行。感谢Martin Smith提供此信息。所以在这个版本中,你最好放弃并重新创建列。

在以前的版本中,您可以尝试这样的事情:

WHILE 1 = 1 BEGIN
   WITH T AS (
      SELECT TOP (10000) *
      FROM dbo.YourTable
      WHERE
         T.Col1 IS NULL
         AND T.COl2 IS NULL
   )
   UPDATE T
   SET
      T.Col1 = '1',
      T.Col2 = '1'
   ;
   IF @@RowCount < 10000 BREAK; -- a trick to save one iteration most times
END;

这可能需要很长时间才能运行,但其好处是它不会长时间锁定桌面。索引和通常行大小的确切组合也会影响它的执行效果。要更新的行数的最佳位置永远不会是恒定的。它可能是50,000或2,000。我曾经在这样的分块操作中尝试过不同的计数,发现5,000或10,000通常非常接近最佳尺寸。

根据过滤索引的SQL Server(2008及更高版本)版本,上述查询也可能会受益:

CREATE UNIQUE NONCLUSTERED INDEX IX_YourTable ON dbo.YourTable (ClusteredColumns)
   WHERE Col1 IS NULL AND COl2 IS NULL;

完成后,删除索引。

请注意,如果您使用默认值NOT NULL指定了两个新列,则会在创建列期间添加值 - 之后可以删除默认值:

ALTER TABLE dbo.YourTable ADD Col1 char(1)
   NOT NULL CONSTRAINT DF_YourTable_Col1 DEFAULT ('1');

与最后添加NULL列不同,这可能会进行快速拆分,这可能需要花费大量时间,因此在您的250M行表中这可能不是一个选项。

更新要解决布莱恩的评论:

  1. 以10,000个小批量进行此操作的理由是更新的开销和开销的负面影响&#34;在很大程度上改善了。是的,确实,这将是很多活动 - 但它不会长时间阻止,这就是这样一项活动的第一个影响性能的因素:长时间阻塞。

  2. 我们很多了解此查询的锁定潜力:UPDATE EXCLUSIVE锁定,并且先前的点应该将此类的任何有害影响保持在最低限度。如果我有其他锁定问题,请分享。

  3. 过滤后的索引会有所帮助,因为它只允许读取索引的几页,然后在巨型表中读取搜索。由于更新,为真,因此必须维护过滤的索引以删除更新的行,因为它们不再符合条件,这确实增加了更新的写入部分的成本。这听起来很糟糕,直到你意识到上面批量UPDATE的最大部分,没有某种索引,每次都是表扫描。给定250M行,这需要与整个表的12,500次完整扫描相同的资源!所以我建议使用索引DOES工作,并且是手动遍历聚集索引的一种简单易用的快捷方式。

  4. 索引的基本规律&#34;对于那些有大量写入动作的表而言,它们不好用。您正在考虑正常的OLTP访问模式,其中可以使用搜索找到正在更新的行,然后对于写入,表上的每个附加索引确实会创建之前不存在的开销。将此与我之前的观点中的解释进行比较。即使过滤后的索引使得UPDATE部分占用每行I / O的5倍(可疑),仍然会将的I / O减少超过2,500次!

  5. 评估更新对性能的影响非常重要,特别是如果表格非常繁忙并且不断使用。如果需要,在非工作时间(如果存在的话)安排它就像你建议的那样基本意义。

    我的建议中的一个潜在弱点是,在SQL 2008及更低版本中,添加过滤后的索引可能需要很长时间 - 尽管可能不是,因为它是一个非常窄的索引并且将以群集顺序编写(可能与一次扫描!)。因此,如果创建时间过长,则可以选择:手动遍历聚簇索引。这可能是这样的:

    DECLARE @ClusteredID int = 0; --assume clustered index is a single int column
    DECLARE @Updated TABLE (
       ClusteredID int NOT NULL
    );
    
    WHILE 1 = 1 BEGIN
       WITH T AS (
          SELECT TOP (10000) *
          FROM dbo.YourTable
          WHERE ClusteredID > @ClusteredID -- the "walking" part
          ORDER BY ClusteredID -- also crucial for "walking"
       )
       UPDATE T
       SET
          T.Col1 = '1',
          T.Col2 = '1'
       OUTPUT Inserted.ClusteredID INTO @Updated
       ;
    
       IF @@RowCount = 0 BREAK;
    
       SELECT @ClusteredID = Max(ClusteredID)
       FROM @Updated
       ;
    
       DELETE @Updated;
    END;
    

    你去:没有索引,一直寻找,只有一个有效的整个表扫描(处理表变量的一小部分开销)。如果ClusteredID列密集,您甚至可以省去表变量,只需在每个循环结束时手动添加10,000个。

    您提供的更新表明您的聚集索引中有5列。这是一个更新的脚本,用于说明您可以如何适应:

    DECLARE -- Five random data types seeded with guaranteed low values
       @Clustered1 int = 0,
       @Clustered2 int = 0,
       @Clustered3 varchar(10) = '',
       @Clustered4 datetime = '19000101',
       @Clustered5 int = 0
    ;
    
    DECLARE @Updated TABLE (
       Clustered1 int,
       Clustered2 int,
       Clustered3 varchar(10),
       Clustered4 datetime,
       Clustered5 int
    );
    
    WHILE 1 = 1 BEGIN
       WITH T AS (
          SELECT TOP (10000) *
          FROM dbo.YourTable
          WHERE
             Clustered1 > @Clustered1
             OR (
                Clustered1 = @Clustered1
                AND (
                   Clustered2 > @Clustered2
                   OR (
                      Clustered2 = @Clustered2
                      AND (
                         Clustered3 > @Clustered3
                         OR (
                            Clustered3 = @Clustered3
                            AND (
                               Clustered4 > @Clustered4
                               OR (
                                  Clustered4 = @Clustered4
                                  AND Clustered5 > @Clustered5
                               )
                            )
                         )
                      )
                   )
                )
             )
          ORDER BY
             Clustered1, -- also crucial for "walking"
             Clustered2,
             Clustered3,
             Clustered4,
             Clustered5
       )
       UPDATE T
       SET
          T.Col1 = '1',
          T.Col2 = '1'
       OUTPUT
          Inserted.Clustered1,
          Inserted.Clustered2,
          Inserted.Clustered3,
          Inserted.Clustered4,
          Inserted.Clustered5
       INTO @Updated
       ;
    
       IF @@RowCount < 10000 BREAK;
    
       SELECT TOP (1)
         @Clustered1 = Clustered1
         @Clustered2 = Clustered2,
         @Clustered3 = Clustered3,
         @Clustered4 = Clustered4,
         @Clustered5 = Clustered5
       FROM @Updated
       ORDER BY
          Clustered1,
          Clustered2,
          Clustered3,
          Clustered4,
          Clustered5
       ;
    
       DELETE @Updated;
    END;
    

    如果您发现某种特殊方式无效,请尝试另一种方法。在更深层次上理解数据库系统将带来更好的想法和卓越的解决方案。我知道深层嵌套的WHERE条件是一种愚蠢的行为。您也可以尝试以下尺寸 - 这完全相同,但很多更难理解,所以我不能真正推荐它,即使添加额外的列非常容易。

    WITH T AS (
       SELECT TOP (10000) *
       FROM
          dbo.YourTable T
       WHERE
          122 <=
             CASE WHEN Clustered1 > @Clustered1 THEN 172 WHEN Clustered1 = @Clustered1 THEN 81 ELSE 0 END
             + CASE WHEN Clustered2 > @Clustered2 THEN 54 WHEN Clustered1 = @Clustered2 THEN 27 ELSE 0 END
             + CASE WHEN Clustered3 > @Clustered3 THEN 18 WHEN Clustered3 = @Clustered3 THEN 9 ELSE 0 END
             + CASE WHEN Clustered4 > @Clustered4 THEN 6 WHEN Clustered4 = @Clustered4 THEN 3 ELSE 0 END
             + CASE WHEN Clustered5 > @Clustered5 THEN 2 WHEN Clustered5 = @Clustered5 THEN 1 ELSE 0 END
       ORDER BY
          Clustered1, -- also crucial for "walking"
          Clustered2,
          Clustered3,
          Clustered4,
          Clustered5
    )
    UPDATE T
    SET
       T.Col1 = '1',
       T.Col2 = '1'
    OUTPUT
       Inserted.Clustered1,
       Inserted.Clustered2,
       Inserted.Clustered3,
       Inserted.Clustered4,
       Inserted.Clustered5
    INTO @Updated
    ;
    

    我有很多次使用这个确切的&#34;小批量的聚集索引来对巨型表进行更新&#34;策略对生产数据库没有影响。