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


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。

插入和更新一些行

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 |
+----+-------+------------------+
  • RowUpdateCounterID为ID的行1,2应保持不变,因为这些行未更改。
  • RowUpdateCounter具有ID的行3,4应更改,因为它们已更新。
  • RowUpdateCounterID为ID的行5,6应更改,因为已将其插入。
  • RowUpdateCounter所有更改的行都应大于4(RowUpdateCounter序列中的最后一个)。

新值(5,6,7,8)分配给更改后的行的顺序并不重要。新值可以有空白,例如15,26,47,58,但绝不能减少。

数据库中有几个带有此类计数器的表。它们是否都使用单个全局序列作为其编号,还是每个表都有其自己的序列都没关系。


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

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

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


我现在的做法相当丑陋。

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

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

INSERTUPDATEMERGE查询我设置RowUpdateCounter为0,这是通过在触发正确的值替换。在???查询中的以上都是0

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


4
您可以使用行版本/时间戳吗?这是一个二进制字段,但是每次更新行时值都会更改
James Z

@JamesZ,我需要知道行的更改顺序。同步过程从表的过时副本中读取MAX计数器,然后知道仅获取Counter大于该值的行。该rowversion不会给我这个可能性,如果我理解正确的是什么?它是保证将不断增加?
弗拉基米尔·巴拉诺夫


谢谢@MartinSmith,我完全忘记了rowversion。看起来很诱人。我唯一关心的是,到目前为止,我所见过的所有使用示例都围绕着检测单行是否发生更改。我需要知道什么是一个有效的方式设置,因为某一个时刻更改的行。此外,是否有可能错过更新?
弗拉基米尔·巴拉诺夫

@MartinSmith time = 0:最后一个rowversion值是122。time = 1:事务A更新一行,其rowversion更改为123,A尚未提交。time = 2:事务B更新另一行,其rowversion更改为124。time = 3:B提交。time = 4:同步过程运行并获取rowversion> 122的所有行,这意味着行仅由更新B。时间= 5:A提交。结果:更改A将永远不会被同步过程接收。我错了吗?也许聪明地使用MIN_ACTIVE_ROWVERSION会有所帮助吗?
弗拉基米尔·巴拉诺夫(Fladimir Baranov)

Answers:


5

您可以ROWVERSION为此使用一列。

该文件指出

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

值是BINARY(8),您应将其视为,BINARY而不是BIGINT在将0x7FFFFFFFFFFFFFFF其视为带符号的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] 

谢谢。阅读文档后,我觉得不是@@DBTS应该有MIN_ACTIVE_ROWVERSION(),如果使用MIN_ACTIVE_ROWVERSION()比较<=应该成为<>>=
弗拉基米尔·巴拉诺夫(Fladimir Baranov)

根据文档@@DBTSMIN_ACTIVE_ROWVERSION()如果有活动的未提交交易,则之间存在实质性差异。如果应用程序使用@@DBTS而不是MIN_ACTIVE_ROWVERSION,则可能会丢失发生同步时处于活动状态的更改。
弗拉基米尔·巴拉诺夫(Fladimir Baranov)

@VladimirBaranov-是的,同意,已编辑。
马丁·史密斯

-2

您是否尝试过使用该IDENTITY选项?

例如:

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

哪里

  • 1->起始值
  • 2->每个新行都以此递增

这类似于Oracle中的SEQUENCE。


SQL Server没有任何“ AUTOINCREMENT选项”
Martin Smith

是。Access支持。SQL Server支持IDENTITY选项。我已经在上面更新了我的回复。谢谢 !!
Bibhuti Bhusan Padhi

4
IDENTITY对于更新和插入都自动递增的功能并没有做什么。
马丁·史密斯

@BibhutiBhusanPadhi,我需要知道哪些行已更新。我看不到有多么简单IDENTITY可以提供帮助。
弗拉基米尔·巴拉诺夫(Fladimir Baranov)
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.