16

データベースのテーブルに新しい列を追加する必要があります。テーブルには約 1 億 4000 万行が含まれており、データベースをロックせずに続行する方法がわかりません。

データベースは本番環境にあるため、可能な限りスムーズに実行する必要があります。

私は多くのことを読みましたが、これが危険な操作であるかどうかについて、実際に答えを得たことはありません. 新しい列は NULL 可能であり、デフォルトは 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 ビットマップは、現在行にあるすべての列の null 可能性を表していると言いたいのですが、心に手を置いて、それが間違いなく真実であるとは言えません。

編集 -> @MartinSmith は、行が変更されたときにのみ NULL ビットマップが展開されることを指摘しました。ただし、彼も指摘しているように、行のサイズが SQL Server 2012 の 8060 バイトの制限を超えて拡大した場合でも、長時間のロックが必要になる場合があります。ありがとうございました*2。

2 番目の警告:

試して。

3 つ目の最後の警告:

いいえ、テストしてください。

于 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 行) 埋めます。テーブルを積極的にブロックしないように、各サイクルの後に 1 秒待ちます。クラスター化された主キーとして int 列「ID」があります

最後に、すべての新しい列がいっぱいになったら、それを NOT NULL に変更します

于 2013-11-12T09:19:56.043 に答える
1

私が通常行う1つの方法があります-そのテーブルをエクスポートし、ローカルで新しい列を作成し、テーブル名の名前を変更してから、テーブルテーブルをインポートし、既存のテーブルの名前を変更して、最初のテーブル名を元の名前に変換します。

于 2013-11-12T09:21:57.700 に答える