変更された行ごとにカウンターを増やす


8

SEQUENCE機能のないSQL Server 2008 Standardを使用しています。

外部システムは、メインデータベースのいくつかの専用テーブルからデータを読み取ります。外部システムはデータのコピーを保持し、データの変更を定期的にチェックして、そのコピーを更新します。

同期を効率的にするために、前回の同期以降に更新または挿入された行のみを転送したいと思います。(行が削除されることはありません)。前回の同期以降に更新または挿入された行を知るために、各テーブルにbigintRowUpdateCounterがあります。

行が挿入または更新されるたびに、そのRowUpdateCounter列の数が変化するという考え方です。RowUpdateCounter列に入る値は、増え続ける数列から取得する必要があります。RowUpdateCounter列の値は一意である必要があり、テーブルに格納される各新しい値は、以前のどの値よりも大きくなければなりません。

望ましい動作を示すスクリプトをご覧ください。

スキーマ

CREATE TABLE [dbo].[Test](
    [ID] [int] NOT NULL,
    [Value] [varchar](50) NOT NULL,
    [RowUpdateCounter] [bigint] NOT NULL,
CONSTRAINT [PK_Test] PRIMARY KEY CLUSTERED
(
    [ID] ASC
))
GO

CREATE UNIQUE NONCLUSTERED INDEX [IX_RowUpdateCounter] ON [dbo].[Test]
(
    [RowUpdateCounter] ASC
)
GO

いくつかの行を挿入

INSERT INTO [dbo].[Test]
    ([ID]
    ,[Value]
    ,[RowUpdateCounter])
VALUES
(1, 'A', ???),
(2, 'B', ???),
(3, 'C', ???),
(4, 'D', ???);

期待される結果

+----+-------+------------------+
| ID | Value | RowUpdateCounter |
+----+-------+------------------+
|  1 | A     |                1 |
|  2 | B     |                2 |
|  3 | C     |                3 |
|  4 | D     |                4 |
+----+-------+------------------+

で生成される値はRowUpdateCounter、たとえばとは異なる場合があり5, 3, 7, 9ます。空のテーブルから始めたため、一意であり、0より大きい必要があります。

一部の行のINSERTおよびUPDATE

DECLARE @NewValues TABLE (ID int NOT NULL, Value varchar(50));
INSERT INTO @NewValues (ID, Value) VALUES
(3, 'E'),
(4, 'F'),
(5, 'G'),
(6, 'H');

MERGE INTO dbo.Test WITH (HOLDLOCK) AS Dst
USING
(
    SELECT ID, Value
    FROM @NewValues
)
AS Src ON Dst.ID = Src.ID
WHEN MATCHED THEN
UPDATE SET
     Dst.Value            = Src.Value
    ,Dst.RowUpdateCounter = ???
WHEN NOT MATCHED BY TARGET THEN
INSERT
    (ID
    ,Value
    ,RowUpdateCounter)
VALUES
    (Src.ID
    ,Src.Value
    ,???)
;

期待される結果

+----+-------+------------------+
| ID | Value | RowUpdateCounter |
+----+-------+------------------+
|  1 | A     |                1 |
|  2 | B     |                2 |
|  3 | E     |                5 |
|  4 | F     |                6 |
|  5 | G     |                7 |
|  6 | H     |                8 |
+----+-------+------------------+
  • RowUpdateCounter1,2これらの行は変更されなかったため、IDのある行はそのままにしておく必要があります。
  • RowUpdateCounterIDの行3,4は更新されたため、変更する必要があります。
  • RowUpdateCounter5,6挿入されたため、IDのある行は変更されます。
  • RowUpdateCounterすべての変更された行は4(RowUpdateCounterシーケンスの最後)より大きい必要があります。

5,6,7,8変更された行に新しい値()が割り当てられる順序は、実際には重要ではありません。新しい値にはギャップなどがありますが、15,26,47,58減少することはありません。

データベースには、このようなカウンターを持つテーブルがいくつかあります。それらのすべてが番号に単一のグローバルシーケンスを使用するか、各テーブルに独自のシーケンスがあるかは問題ではありません。


次の理由により、整数カウンターの代わりに日時スタンプの列を使用したくありません。

  • サーバーのクロックは、順方向と逆方向の両方にジャンプできます。特に仮想マシン上にある場合。

  • のようなシステム関数によって返される値は、SYSDATETIME影響を受けるすべての行で同じです。同期プロセスは、変更をバッチで読み取ることができる必要があります。たとえば、バッチサイズが3行の場合、MERGE上記の手順の後、同期プロセスは行のみを読み取りますE,F,G。同期プロセスが次回実行されるときに、行から続行されHます。


私が今やっていることはかなり醜いです。

SEQUENCESQL Server 2008 にはないので、この回答に示すようにSEQUENCE、専用のテーブルでをエミュレートします。これ自体はかなり醜く、一度に1つの数値ではなくバッチの数値を生成する必要があるという事実により、さらに悪化します。IDENTITY

次に、を使用してINSTEAD OF UPDATE, INSERT各テーブルにトリガーをRowUpdateCounter設定し、そこに必要な数のセットを生成します。

INSERTUPDATEおよびMERGEIセットを照会RowUpdateCounterトリガーに正しい値で置換され、0。???上記のクエリではあります0

それは機能しますが、より簡単な解決策はありますか?


4
行バージョン/タイムスタンプを使用できますか?バイナリフィールドですが、行が更新されるたびに値が変わります
James Z

@JamesZ、行が変更された順序を知る必要があります。同期プロセスは、テーブルの古いコピーからMAXカウンターを読み取り、その値を超えるカウンターを持つ行のみをフェッチすることを認識します。rowversion私が正しくそれが何であるかを理解していれば、私はこの可能性を明らかにしなかった...それはますます増加することが保証されていますか?
Vladimir Baranov 2016


@MartinSmithありがとう、私は完全に忘れていましたrowversion。とても魅力的に見えます。私の唯一の懸念は、これまでに見たその使用例はすべて、単一の行が変更されたかどうかを検出することに関係しています。特定の瞬間以降に変更された行のセットを効率的に知る方法が必要です。また、アップデートを見逃す可能性はありますか?
Vladimir Baranov

@MartinSmith time = 0:最後の行バージョンの値は、たとえば122です。time= 1:トランザクションAは行を更新し、行バージョンは123に変更されますが、Aまだコミットされていません。time = 2:トランザクションはB別の行を更新し、その行バージョンは124に変更されます。time= 3:Bコミットします。time = 4:同期プロセスが実行され、rowversion> 122のすべての行がフェッチされBます。つまり、によってのみ行が更新されます。time = 5:Aコミット。結果:による変更Aは、同期プロセスによって取得されません。私が間違っている?多分いくつかの巧妙な使用MIN_ACTIVE_ROWVERSIONが役立ちますか?
Vladimir Baranov 2016

回答:


5

ROWVERSIONこれには列を使用できます。

ドキュメントには、

各データベースには、データベース内のrowversion列を含むテーブルで実行される挿入または更新操作ごとに増分されるカウンターがあります。

値は次のとおりでBINARY(8)あり、署名されたものとして扱われる場合に、それが処理を開始した後ではBINARYなく、それらを検討する必要があります。BIGINT0x7FFFFFFFFFFFFFFF0x80...-9223372036854775808bigint

完全に機能する例を以下に示します。ROWVERSION更新が多い場合、列のインデックスを維持するにはコストがかかるため、コストの価値があるかどうかを確認するためにワークロードをテストする場合としない場合の両方でワークロードをテストすることができます。

CREATE TABLE [dbo].[Test]
  (
     [ID]               [INT] NOT NULL CONSTRAINT [PK_Test] PRIMARY KEY,
     [Value]            [VARCHAR](50) NOT NULL,
     [RowUpdateCounter] [ROWVERSION] NOT NULL UNIQUE NONCLUSTERED
  )

INSERT INTO [dbo].[Test]
            ([ID],
             [Value])
VALUES     (1,'Foo'),
            (2,'Bar'),
            (3,'Baz');

DECLARE @RowVersion_LastSynch ROWVERSION = MIN_ACTIVE_ROWVERSION();

UPDATE [dbo].[Test]
SET    [Value] = 'X'
WHERE  [ID] = 2;

DECLARE @RowVersion_ThisSynch ROWVERSION = MIN_ACTIVE_ROWVERSION();

SELECT *
FROM   [dbo].[Test]
WHERE  [RowUpdateCounter] >= @RowVersion_LastSynch
       AND RowUpdateCounter < @RowVersion_ThisSynch;

/*TODO: Store @RowVersion_ThisSynch somewhere*/

DROP TABLE [dbo].[Test] 

ありがとうございました。ドキュメンテーションを読んだ後、私は@@DBTSあるべきだと思いますMIN_ACTIVE_ROWVERSION()、そしてMIN_ACTIVE_ROWVERSION()比較<=を使うならば<>なるようになるべきだと思います>=
Vladimir Baranov

ドキュメントによると、アクティブなコミットされていないトランザクションがあるかどうかに重要な違いが@@DBTSありMIN_ACTIVE_ROWVERSION()ます。アプリケーションがでは@@DBTSなくを使用MIN_ACTIVE_ROWVERSIONしている場合、同期が発生したときにアクティブになっている変更を見逃す可能性があります。
Vladimir Baranov 2016

@VladimirBaranov-うん、同意、編集。
マーティンスミス

-2

IDENTITYオプションを使用してみましたか?

例えば:

[RowUpdateCounter] [bigint] NOT NULL IDENTITY(1,2)

どこ

  • 1->開始値
  • 2->新しい各行はこれによって増加します

これは、OracleのSEQUENCEに似ています。


SQL Serverには "AUTOINCREMENTオプション"がありません
Martin Smith

はい。Accessでサポートされています。SQLサーバーはIDENTITYオプションをサポートしています。上記の返信を更新しました。ありがとう!!
Bibhuti Bhusan Padhi 2016

4
IDENTITY、更新と挿入の両方で自動的にインクリメントするために必要なことを行いません。
マーティン・スミス

@BibhutiBhusanPadhi、どの行が更新されたかを知る必要があります。どれほど単純なIDENTITYことが役立つかわかりません。
Vladimir Baranov 2016
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.