16

我需要在我的数据库中的表中添加一个新列。该表包含大约 1.4 亿行,我不确定如何在不锁定数据库的情况下继续操作。

数据库正在生产中,这就是为什么它必须尽可能顺利。

我读了很多书,但从来没有真正得到答案,如果这是一个有风险的操作。新列可以为空,默认可以为 NULL。据我了解,如果新列需要默认值,则会出现更大的问题。

我真的很感激关于这个问题的一些直截了当的答案。这是可行的还是不可行的?

4

4 回答 4

9

是的,这是非常可行的。

添加可接受 NULL 且没有默认值的列不需要长时间运行的锁来将数据添加到表中。

如果您提供默认值,则 SQL Server 必须去更新每条记录,以便将新列值写入行。

一般如何工作:

+---------------------+------------------------+-----------------------+
| Column is Nullable? | Default Value Supplied | Result                |
+---------------------+------------------------+-----------------------+
| Yes                 | No                     | Quick Add (caveat)    |
| Yes                 | Yes                    | Long running lock     |
| No                  | No                     | Error                 |
| No                  | Yes                    | Long running lock     |
+---------------------+------------------------+-----------------------+

警告位:

我不记得当您添加一个导致 NULL 位图大小扩大的列时会发生什么。我想说 NULL 位图表示当前行中所有列的可空性,但我不能把手放在心里说这绝对是真的。

编辑-> @MartinSmith 指出 NULL 位图只会在行更改时扩展,非常感谢。但是,正如他还指出的那样,如果行的大小超过 SQL Server 2012 中的 8060 字节限制,则可能仍需要长时间运行的锁。非常感谢 * 2。

第二个警告:

测试一下。

第三个也是最后一个警告:

不是真的,测试一下。

于 2013-11-12T09:48:08.110 回答
6

我的示例是如何在表中添加数千万行的新列并按默认值填充它而无需长时间运行锁定

USE [MyDB]
GO

ALTER TABLE [dbo].[Customer] ADD [CustomerTypeId] TINYINT NULL
GO
ALTER TABLE [dbo].[Customer] ADD CONSTRAINT [DF_Customer_CustomerTypeId] DEFAULT 1 FOR [CustomerTypeId]
GO
DECLARE @batchSize bigint = 5000
    ,@rowcount int
    ,@MaxID int;

SET @rowcount = 1
SET @MaxID = 0

WHILE @rowcount > 0
BEGIN
    ;WITH upd as (
        SELECT TOP (@batchSize)
            [ID]
            ,[CustomerTypeId]
        FROM [dbo].[Customer] (NOLOCK)
        WHERE [CustomerTypeId] IS NULL
            AND [ID] > @MaxID
        ORDER BY [ID])

    UPDATE upd
          SET [CustomerTypeId] = 1
              ,@MaxID = CASE WHEN [ID] > @MaxID THEN [ID] ELSE @MaxID END

    SET @rowcount = @@ROWCOUNT
    WAITFOR DELAY '00:00:01'
END;

ALTER TABLE [dbo].[Customer]  ALTER COLUMN [CustomerTypeId] TINYINT NOT NULL;
GO

ALTER TABLE [dbo].[Customer] ADD [CustomerTypeId] TINYINT NULL仅更改元数据(Sch-M 锁)并且锁定时间不取决于表中的行数

之后,我按默认值小部分(5000 行)填充一个新列。我在每个循环后等待一秒钟,以免过于激进地阻塞桌子。我有一个 int 列“ID”作为主聚集键

最后,当所有新列都填满时,我将其更改为 NOT NULL

于 2013-11-12T09:19:56.043 回答
5

没有人能说出操作需要多少时间,因为这毕竟取决于许多其他因素。

您不必担心操作本身,因为 SQL Server 一切正常:

数据库引擎在表数据定义语言 (DDL) 操作期间使用架构修改 (Sch-M) 锁,例如添加列或删除表。在它被持有期间,Sch-M 锁阻止对表的并发访问。这意味着 Sch-M 锁会阻止所有外部操作,直到锁被释放。

我从未ALTER对如此大量的数据进行过操作,我能给出的唯一建议是在与数据库的连接不多时(在夜间)进行操作。

编辑:

在这里您可以找到有关您的问题的更多信息。一般来说,Matt Whitfield是对的

向表中添加列会导致数据大小操作(即修改表中每一行的操作)的唯一情况是新列具有非空默认值。

什么时候

新列可以为空,默认为 NULL。表的元数据记录了新列存在但可能不在记录中的事实。这就是为什么空位图还具有该特定记录中列数的计数。SQL Server 可以确定记录中是否存在列。所以——这不是一个数据大小操作——当添加新列时,现有的表记录不会更新。仅当为某些其他操作更新记录时才会更新记录。

于 2013-11-12T09:33:52.060 回答
1

我通常有一种方法 - 导出该表并在本地创建新列并重命名表名,然后导入表表,然后重命名现有表并将第一个表名转换为原始表名。

于 2013-11-12T09:21:57.700 回答