为每个更改的行增加一个计数器

Vla*_*nov 8 sql-server-2008 sql-server

我使用的是 SQL Server 2008 Standard,它没有 SEQUENCE功能的。

外部系统从主数据库的几个专用表中读取数据。外部系统保留一份数据副本并定期检查数据中的更改并刷新其副本。

为了使同步高效,我只想传输自上次同步以来更新或插入的行。(这些行永远不会被删除)。要知道自上次同步以来更新或插入了哪些行,有一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
Run Code Online (Sandbox Code Playgroud)

插入一些行

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

预期结果

+----+-------+------------------+
| ID | Value | RowUpdateCounter |
+----+-------+------------------+
|  1 | A     |                1 |
|  2 | B     |                2 |
|  3 | C     |                3 |
|  4 | D     |                4 |
+----+-------+------------------+
Run Code Online (Sandbox Code Playgroud)

中生成的值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
    ,???)
;
Run Code Online (Sandbox Code Playgroud)

预期结果

+----+-------+------------------+
| ID | Value | RowUpdateCounter |
+----+-------+------------------+
|  1 | A     |                1 |
|  2 | B     |                2 |
|  3 | E     |                5 |
|  4 | F     |                6 |
|  5 | G     |                7 |
|  6 | H     |                8 |
+----+-------+------------------+
Run Code Online (Sandbox Code Playgroud)
  • RowUpdateCounter对于带有 ID 的行,1,2应保持原样,因为这些行未更改。
  • RowUpdateCounter具有 ID 的行3,4应该更改,因为它们已更新。
  • RowUpdateCounter具有 ID 的行5,6应该更改,因为它们已被插入。
  • RowUpdateCounter对于所有更改的行应该大于 4(RowUpdateCounter序列中的最后一个)。

将新值 ( 5,6,7,8) 分配给更改的行的顺序并不重要。新值可以有间隙,例如15,26,47,58,但它们永远不会减少。

数据库中有几个带有此类计数器的表。如果它们都使用单一的全局序列作为它们的数字,或者每个表都有自己的单独序列,这并不重要。


我不想使用带有日期时间戳的列而不是整数计数器,因为:

  • 服务器上的时钟可以向前和向后跳跃。特别是当它在虚拟机上时。

  • 系统函数返回的值SYSDATETIME对于所有受影响的行都是相同的。同步过程应该能够批量读取更改。例如,如果批处理大小为 3 行,则在上述MERGE步骤之后,同步过程将只读取 rows E,F,G。下次运行同步过程时,它将从 row 继续H


我现在这样做的方式相当丑陋。

由于SEQUENCESQL Server 2008 中没有,因此我SEQUENCE通过专用表模拟,IDENTITY本答案所示。这本身就很丑陋,而且由于我需要一次生成的不是一个数字而是一批数字这一事实而加剧。

然后,我INSTEAD OF UPDATE, INSERT在每个表上都有一个触发器,并在RowUpdateCounter那里生成所需的数字集。

INSERT,UPDATEMERGE查询中我设置RowUpdateCounter为 0,它被触发器中的正确值替换。在???查询中的以上都是0

它有效,但有更简单的解决方案吗?

Mar*_*ith 5

您可以ROWVERSION为此使用一列。

该文件指出

每个数据库都有一个计数器,该计数器随着在数据库中包含 rowversion 列的表上执行的每个插入或更新操作而递增。

这些值是BINARY(8),您应该将它们视为,BINARY而不是BIGINT0x7FFFFFFFFFFFFFFF它继续0x80...并开始工作之后,-9223372036854775808如果被视为已签名bigint.

下面是一个完整的工作示例。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] 
Run Code Online (Sandbox Code Playgroud)