Sql server 如何在SQLServer中向大型表中添加NOTNULL列?

Sql server 如何在SQLServer中向大型表中添加NOTNULL列?,sql-server,Sql Server,要向包含多条记录的表中添加NOTNULL列,需要应用默认约束。如果表非常大,此约束会导致整个ALTER TABLE命令运行很长时间。这是因为: 假设: 默认约束修改现有记录。这意味着数据库需要增加每个记录的大小,这会导致它将完整数据页上的记录转移到其他数据页,这需要时间 默认更新作为原子事务执行。这意味着需要增加事务日志,以便在必要时执行回滚 事务日志跟踪整个记录。因此,即使只修改了单个字段,日志所需的空间也将基于整个记录的大小乘以现有记录的大小。这意味着,即使两个表的记录总数相同,向具有小记录

要向包含多条记录的表中添加NOTNULL列,需要应用默认约束。如果表非常大,此约束会导致整个ALTER TABLE命令运行很长时间。这是因为:

假设:

  • 默认约束修改现有记录。这意味着数据库需要增加每个记录的大小,这会导致它将完整数据页上的记录转移到其他数据页,这需要时间
  • 默认更新作为原子事务执行。这意味着需要增加事务日志,以便在必要时执行回滚
  • 事务日志跟踪整个记录。因此,即使只修改了单个字段,日志所需的空间也将基于整个记录的大小乘以现有记录的大小。这意味着,即使两个表的记录总数相同,向具有小记录的表中添加列也比向具有大记录的表中添加列要快
  • 可能的解决办法:

  • 吸取教训,等待过程完成。只需确保将超时时间设置为很长。问题在于,根据记录的不同,这可能需要数小时或数天的时间
  • 添加列,但允许NULL。然后,运行更新查询以设置现有行的默认值。请勿更新*。一次更新一批记录,否则您将遇到与解决方案1相同的问题。这种方法的问题是,当您知道这是一个不必要的选项时,您最终会得到一个允许NULL的列。我相信有一些最佳实践文档指出,除非有必要,否则不应该有允许NULL的列
  • 创建具有相同架构的新表。将列添加到该架构中。从原始表传输数据。删除原始表并重命名新表。我不确定这怎么会比#1好
  • 问题:

  • 我的假设正确吗
  • 这些是我唯一的解决办法吗?如果是,哪一个是最好的?如果没有,我还能做什么

  • 我认为这取决于您正在使用的SQL风格,但是如果您选择了选项2,但最后使用默认值将table table改为notnull,会怎么样


    因为它看到所有的值都不是空的,所以它会很快吗?

    垂直分割表格。这意味着您将有两个表,具有相同的主键,并且记录数完全相同。。。一个将是您已经拥有的,另一个将只有键和新的非空列(具有默认值)。
    修改所有Insert、Update和delete代码,使它们保持两个表的同步。。。如果需要,您可以创建一个视图,将两个表“连接”在一起,以创建两个表的单一逻辑组合,该组合看起来就像客户端Select语句的单一表…

    以下是我将尝试的内容:

    • 对数据库进行完整备份
    • 添加新列,允许空值-不要设置默认值
    • 设置简单恢复,它在提交每个批时立即截断传输日志
    • SQL是:ALTER DATABASE XXX SET RECOVERY SIMPLE
    • 如上文所述,分批运行更新,并在每次更新后提交
    • 重置新列以不再允许空值
    • 回到正常的完全恢复状态
    • SQL为:ALTER DATABASE XXX SET RECOVERY FULL
    • 再次备份数据库

    简单恢复模型的使用并没有停止日志记录,但它显著降低了其影响。这是因为服务器在每次提交后都会丢弃恢复信息。

    我会使用游标而不是更新。游标将批量更新所有匹配的记录,一条记录一条记录地更新——这需要时间,但不会锁定表

    如果要避免锁定,请使用等待

    另外,我不确定默认约束是否会更改现有行。 可能不是空约束与作者描述的默认原因一起使用

    如果它改变了,最后添加它 所以伪代码看起来像:

    -- without NOT NULL constrain -- we will add it in the end
    ALTER TABLE table ADD new_column INT DEFAULT 0
    
    DECLARE fillNullColumn CURSOR LOCAL FAST_FORWARD
        SELECT 
            key
        FROM
            table WITH (NOLOCK)
        WHERE
            new_column IS NULL
    
    OPEN fillNullColumn
    
    DECLARE 
        @key INT
    
    FETCH NEXT FROM fillNullColumn INTO @key
    
    WHILE @@FETCH_STATUS = 0 BEGIN
         UPDATE
             table WITH (ROWLOCK)
         SET
             new_column = 0 -- default value
         WHERE
             key = @key
    
         WAIT 00:00:05 --wait 5 seconds, keep in mind it causes updating only 12 rows per minute
    
         FETCH NEXT FROM fillNullColumn INTO @key
    END
    
    CLOSE fillNullColumn
    DEALLOCATE fillNullColumn
    
    ALTER TABLE table ALTER COLUMN new_column ADD CONSTRAIN xxx
    
    我确信有一些语法错误,但我希望 帮助解决你的问题


    祝你好运

    如果要将列放在同一个表中,只需这样做即可。现在,选项3可能是最好的选择,因为在执行此操作时,您仍然可以使数据库处于“活动”状态。如果您使用选项1,则在操作发生时表被锁定,然后您真的被卡住了

    如果您不在乎该列是否在表中,那么我认为分段方法是次优方法。尽管如此,我还是尽量避免这样做(以至于我不这么做),因为就像Charles Bretana所说的那样,您必须确保找到所有更新/插入该表的位置并对其进行修改。啊

    你可以:

  • 开始一项交易
  • 在原始表上抓取一个写锁,这样就不会有人向它写入
  • 使用新架构创建一个阴影表
  • 传输原始表中的所有数据
  • 执行以重命名旧表
  • 执行以重命名中的新表
  • 最后,提交事务

  • 这种方法的优点是,读者可以在漫长的过程中访问表,并且可以在后台执行任何形式的模式更改

    我也遇到了类似的问题,选择了你的选项2。 这种方式需要20分钟,而另一种方式需要32小时!!!差别很大,谢谢你的提示。 我写了一篇关于它的完整博客文章,但下面是重要的sql:

    Alter table MyTable
    Add MyNewColumn char(10) null default '?';
    go
    
    update MyTable set MyNewColumn='?' where MyPrimaryKey between 0 and 1000000
    go
    update MyTable set MyNewColumn='?' where MyPrimaryKey between 1000000 and 2000000
    go
    update MyTable set MyNewColumn='?' where MyPrimaryKey between 2000000 and 3000000
    go
    ..etc..
    
    Alter table MyTable
    Alter column MyNewColumn char(10) not null;
    
    如果您感兴趣,请访问以下博客:
    我在工作中也遇到了这个问题。我的解决方案是2

    以下是我的步骤(我正在使用SQL Server 2005):

    1) 添加c
    ALTER TABLE MyTable ADD MyColumn varchar(40) DEFAULT('')
    
    ALTER TABLE MyTable WITH NOCHECK
    ADD CONSTRAINT MyColumn_NOTNULL CHECK (MyColumn IS NOT NULL)
    
    GO
    UPDATE TOP(3000) MyTable SET MyColumn = '' WHERE MyColumn IS NULL
    GO 1000
    
     SELECT  table.*,   cast (‘default’ as nvarchar(256)) new_column
     INTO    table_copy 
     FROM    table
    
     DROP TABLE  table
    
     EXEC sp_rename 'table_copy',  ‘table’
    
    Alter table mytable add mycolumn char(1) not null default('N');
    
    ALTER TABLE MyTable ADD MyColumn int default 0
    
    declare @rowcount int = 1
    
    while (@rowcount > 0)
    begin           
    
        UPDATE TOP(10000) MyTable SET MyColumn = 0 WHERE MyColumn IS NULL       
        set @rowcount = @@ROWCOUNT
    
    end
    
    ALTER TABLE MyTable ALTER COLUMN MyColumn int NOT NULL