sql >> データベース >  >> RDS >> Database

IDENTITY列を広げることによる影響の最小化–パート3

    [パート1|パート2|パート3|パート4]

    このシリーズのこれまでのところ、intからアップサイジングした場合のページへの直接的な物理的影響を示してきました。 bigintへ 、次に、いくつかの一般的なブロッカーを介してこの操作を繰り返しました。この投稿では、2つの潜在的な回避策を検討したいと思いました。1つは単純で、もう1つは非常に複雑です。

    簡単な方法

    以前の投稿へのコメントで、雷が少し奪われました。キースモンローは、テーブルを下のネガティブに再シードすることを提案しました。 整数データ型の境界であり、新しい値の容量が2倍になります。これは、DBCC CHECKIDENTを使用して行うことができます :

     DBCC CHECKIDENT(N'dbo.TableName'、RESEED、-2147483648); 

    これは、サロゲート値がエンドユーザーにとって意味がないと仮定すると機能する可能性があります(または、意味がある場合、ユーザーが突然負の数を取得することでびっくりすることはありません)。私はあなたがビューでそれらをだますことができると思います:

     CREATE VIEW dbo.ViewNameAS SELECT ID =CONVERT(bigint、CASE WHEN ID <0 THEN(2147483648 * 2)-1 + CONVERT(bigint、ID)ELSE ID END)FROM dbo.TableName; 

    これは、ID = -2147483648を追加したユーザーが 実際には+2147483648が表示されます 、ID = -2147483647を追加したユーザー +2147483649が表示されます 、 等々。ただし、ユーザーがそのIDを渡したときに必ず逆の計算を行うように、他のコードを調整する必要があります。 、例:

     ALTER PROCEDURE dbo.GetRowByID @ID bigintASBEGIN SET NOCOUNT ON; DECLARE @RealID bigint; SET @RealID =CASE WHEN @ID> 2147483647 THEN @ID-(2147483648 * 2)+ 1 ELSE @ID END; SELECT ID、@ ID / *、その他の列* / FROM dbo.TableName WHERE ID =@RealID; ENDGO 

    私はこの難読化に夢中ではありません。まったく。それは厄介で、誤解を招きやすく、エラーが発生しやすいです。また、代理キー(通常、IDENTITY)を可視化することをお勧めします。 値はエンドユーザーに公開されるべきではないため、顧客24、642、-376、またはゼロの両側にあるはるかに大きな数値であるかどうかを実際に気にする必要はありません。

    この「解決策」は、IDENTITYで注文するコードがどこにも存在しないことも前提としています。 最後に挿入された行を最初に表示するための列、または最も高いIDENTITYを推測するための列 値は最新の行である必要があります。 行うコード IDENTITYの並べ替え順序に依存します 列は、明示的または暗黙的に(クラスター化インデックスの場合は思ったよりも多い可能性があります)、期待どおりの順序で行を表示しなくなります。RESEEDの後に作成されたすべての行が表示されます。 、最初から始めて、RESEEDの前に作成されたすべての行が表示されます 、最初から始めます。

    このアプローチの主な利点は、データ型を変更する必要がないことです。その結果、RESEED 変更には、インデックス、制約、またはインバウンド外部キーへの変更は必要ありません。

    欠点は、もちろん、上記のコード変更に加えて、これは短期的にしか時間を費やさないことです。最終的には、利用可能なすべての負の整数も使い果たします。そして、これが現在のバージョンのテーブルの時間の観点からの耐用年数を2倍にするとは思わないでください。 –多くの場合、データの増加は加速しており、一定ではありません。そのため、次の20億行は、最初の20億行よりもはるかに速く使い果たされます。

    難しい方法

    実行できるもう1つのアプローチは、IDENTITYの使用を停止することです。 完全に列;代わりに、SEQUENCEを使用して変換できます 。新しいbigintを作成できます 列で、デフォルトをSEQUENCEの次の値に設定します 、これらの値をすべて元の列の値で更新し(必要に応じてバッチで)、元の列を削除して、新しい列の名前を変更します。この架空のテーブルを作成して、1行挿入してみましょう:

     CREATE TABLE dbo.SequenceDemo(ID int IDENTITY(1,1)、x char(1)、CONSTRAINT PK_SD_Identity PRIMARY KEY CLUSTERED(ID)); GO INSERT dbo.SequenceDemo(x)VALUES('x'); 

    次に、SEQUENCEを作成します これは、intの上限を超えて始まります:

     CREATE SEQUENCE dbo.BeyondIntAS bigintSTART WITH 2147483648 INCREMENT BY 1; 

    次に、SEQUENCEの使用に切り替えるために必要なテーブルの変更 新しい列の場合:

     BEGIN TRANSACTION; -新しい「ID」列を追加します:ALTER TABLE dbo.SequenceDemo ADD ID2 bigint; GO-新しい列を既存のID値と等しく設定します-大きなテーブルの場合、これをバッチで行う必要があります:UPDATE dbo.SequenceDemo SET ID2 =ID; --これをnull許容にせず、SEQUENCE:ALTER TABLE dbo.SequenceDemo ALTER COLUMN ID2 bigint NOT NULL; ALTER TABLE dbo.SequenceDemo ADD CONSTRAINT DF_SD_Identity DEFAULT NEXT VALUE FOR dbo.BeyondIntFORID2からデフォルトを追加します。 --既存のPK(および任意のインデックス)を削除する必要があります:ALTER TABLE dbo.SequenceDemo DROP CONSTRAINT PK_SD_Identity; --古い列を削除し、新しい列の名前を変更します。ALTERTABLE dbo.SequenceDemo DROP COLUMN ID; EXEC sys.sp_rename N'dbo.SequenceDemo.ID2'、N'ID'、'COLUMN'; --PKをバックアップします:ALTER TABLE dbo.SequenceDemo ADD CONSTRAINT PK_SD_Identity PRIMARY KEY CLUSTERED(ID);トランザクションのコミット;

    この場合、次の挿入で次の結果が得られます(SCOPE_IDENTITY() 有効な値を返さなくなりました):

     INSERT dbo.SequenceDemo(x)VALUES('y'); SELECT Si =SCOPE_IDENTITY(); SELECT ID、x FROM dbo.SequenceDemo; /*結果Si----NULL ID x ---------- -1 x2147483648 y * / 

    テーブルが大きく、ここで説明したように、上記のワンショットトランザクションではなく、新しい列をバッチで更新する必要がある場合(ユーザーがその間にテーブルを操作できるようにするため)、トリガーが必要になりますSEQUENCEをオーバーライドするための場所 挿入された新しい行の値。これにより、呼び出し元のコードに出力されたものと引き続き一致します。 (これは、整数範囲にまだ更新を受け入れる余地があることも前提としています。そうでない場合は、すでに範囲を使い果たしている場合は、ダウンタイムをとる必要があります。または、上記の簡単なソリューションを短期的に使用する必要があります。 。)

    すべてを削除して最初からやり直してから、新しい列を追加してみましょう:

     DROP TABLE dbo.SequenceDemo; DROP SEQUENCE dbo.BeyondInt; GO CREATE TABLE dbo.SequenceDemo(ID int IDENTITY(1,1)、x char(1)、CONSTRAINT PK_SD_Identity PRIMARY KEY CLUSTERED(ID)); GO INSERT dbo .SequenceDemo(x)VALUES('x'); GO CREATE SEQUENCE dbo.BeyondIntAS bigintSTART WITH 2147483648 INCREMENT BY 1; GO ALTER TABLE dbo.SequenceDemo ADD ID2 bigint; GO 

    そして、これが追加するトリガーです:

     CREATE TRIGGER dbo.After_SequenceDemoON dbo.SequenceDemoAFTER INSERTASBEGIN UPDATE sd SET sd.ID2 =sd.ID FROM dbo.SequenceDemo AS sd INNER JOIN insert AS i ON sd.ID =i.ID; END 

    今回は、既存のすべての値が更新され、残りの変更がコミットされるまで、次の挿入で両方の列の整数の低い範囲の行が生成され続けます。

     INSERT dbo.SequenceDemo(x)VALUES('y'); SELECT Si =SCOPE_IDENTITY(); SELECT ID、ID2、x FROM dbo.SequenceDemo; /*結果Si----2 ID ID2 x ---- ---- --1 NULL x2 2 y * / 

    これで、既存のID2の更新を続行できます 新しい行が低い範囲内に挿入され続ける間の値:

     SET NOCOUNT ON; DECLARE @r INT =1; WHILE @r> 0BEGIN BEGIN TRANSACTION; UPDATE TOP(10000)dbo.SequenceDemo SET ID2 =ID WHERE ID2 IS NULL; SET @r =@@ ROWCOUNT;トランザクションのコミット; -チェックポイント; --単純な場合--バックアップログ...--fullENDの場合

    既存のすべての行を更新したら、残りの変更を続行して、トリガーをドロップできます:

     BEGIN TRANSACTION; ALTER TABLE dbo.SequenceDemo ALTER COLUMN ID2 BIGINT NOT NULL; ALTER TABLE dbo.SequenceDemo ADD CONSTRAINT DF_SD_Identity DEFAULT NEXT VALUE FOR dbo.BeyondInt FOR ID2; ALTER TABLE dbo.SequenceDemo DROP CONSTRAINT PK_SD_I DROP COLUMN ID; EXEC sys.sp_rename N'dbo.SequenceDemo.ID2'、N'ID'、' COLUMN'; ALTER TABLE dbo.SequenceDemo ADD CONSTRAINT PK_SD_Identity PRIMARY KEY CLUSTERED(ID); DROP TRIGGER dbo.InsteadOf_SequenceDemo pre> 

    これで、次の挿入で次の値が生成されます:

     INSERT dbo.SequenceDemo(x)VALUES('z'); SELECT Si =SCOPE_IDENTITY(); SELECT ID、x FROM dbo.SequenceDemo; /*結果Si----NULL ID x ---------- -1 x2 y2147483648 z * / 

    SCOPE_IDENTITY()に依存するコードがある場合 、@@IDENTITY 、またはIDENT_CURRENT() 、挿入後にこれらの値が入力されなくなるため、変更する必要があります– OUTPUT 句は、ほとんどのシナリオで引き続き正しく機能するはずです。テーブルがIDENTITYを生成すると信じ続けるためにコードが必要な場合は、 値の場合、トリガーを使用してこれを偽造することができますが、@@IDENTITYにのみ入力できます。 SCOPE_IDENTITY()ではなく挿入時 。ほとんどの場合、@@IDENTITYに依存したくないため、これでも変更が必要になる場合があります。 何でも(したがって、変更を加える場合は、IDENTITYに関するすべての仮定を削除してください。 列全体)。

     CREATE TRIGGER dbo.FakeIdentityON dbo.SequenceDemoINSTEAD OF INSERTASBEGIN SET NOCOUNT ON; DECLARE @lowestID bigint =(SELECT MIN(id)FROMが挿入されました); DECLARE @sql nvarchar(max)=N'DECLARE @foo TABLE(ID bigint IDENTITY(' + CONVERT(varchar(32)、@lowestID)+ N'、1));'; SELECT @sql + =N'INSERT @foo DEFAULT VALUES;' FROM挿入; EXEC sys.sp_executesql @sql; INSERT dbo.SequenceDemo(ID、x)SELECT ID、xFROM挿入;END 

    これで、次の挿入で次の値が生成されます:

     INSERT dbo.SequenceDemo(x)VALUES('a'); SELECT Si =SCOPE_IDENTITY()、Ident =@@ IDENTITY; SELECT ID、x FROM dbo.SequenceDemo; /*結果SiIdent--------- NULL 2147483649 ID x ---------- -1 x2 y2147483648 z2147483649 a * / 

    この回避策を使用しても、他の制約、インデックス、およびインバウンド外部キーを持つテーブルを処理する必要があります。ローカル制約とインデックスは非常に単純ですが、このシリーズの次のパートでは、外部キーのより複雑な状況に対処します。

    機能しないもの、しかし私はそれが機能することを望みます

    ALTER TABLE SWITCH 他の方法では実行が難しいメタデータの変更を行うための非常に強力な方法になる可能性があります。そして、一般的な信念に反して、これは単にパーティション化を伴うだけでなく、EnterpriseEditionに限定されません。次のコードはExpressで機能し、IDENTITYを追加または削除するために人々が使用した方法です。 テーブル上のプロパティ(ここでも、外部キーやその他すべての厄介なブロッカーは考慮されていません)。

     CREATE TABLE dbo.WithIdentity(ID int IDENTITY(1,1)NOT NULL); CREATE TABLE dbo.WithoutIdentity(ID int NOT NULL); ALTER TABLE dbo.WithIdentity SWITCH TO dbo.WithoutIdentity; GO DROP TABLE dbo.WithIdentity; EXEC sys.sp_rename N'dbo.WithoutIdentity'、N'dbo.WithIdentity'、'OBJECT'; 

    これが機能するのは、データ型とnull可能性が完全に一致し、IDENTITYに注意が払われていないためです。 属性。ただし、データ型を混在させてみてください。そうすると、うまく機能しません。

     CREATE TABLE dbo.SourceTable(ID int IDENTITY(1,1)NOT NULL); CREATE TABLE dbo.TrySwitch(ID bigint IDENTITY(1,1)NOT NULL); ALTER TABLE dbo.SourceTable SWITCH TO dbo.TrySwitch; 

    その結果、次のようになります。

    メッセージ4944、レベル16、状態1
    ALTER TABLE SWITCHステートメントが失敗しました。これは、列'ID'のデータ型がソーステーブル'dbo.SourceTable'のデータ型intであり、ターゲットテーブル'dbo.TrySwitch'の型bigintとは異なるためです。

    SWITCHがあれば、それは素晴らしいことです。 操作は、スキーマの唯一の違いが実際に対応するための物理的な変更を*必要としない*このようなシナリオで使用できます(ここでも、パート1で示したように、データは新しいページに再書き込みされますが、そうする必要はありません。

    結論

    この投稿では、既存のIDENTITYを変更する前に時間を購入するための2つの潜在的な回避策を調査しました。 列、またはIDENTITYを放棄する 完全に今はSEQUENCEを支持しています 。これらの回避策のいずれも受け入れられない場合は、この問題に正面から取り組むパート4に注意してください。

    [パート1|パート2|パート3|パート4]


    1. SQLServerデータベース内のシステムテーブルの数をカウントする3つの方法

    2. Varchar2とcharの主な違いは何ですか

    3. SQLServerのCASE式

    4. 引用符で囲まれたプレースホルダーを使用してクエリを使用するにはどうすればよいですか? (perl / postgresql)