Увеличьте счетчик для каждой измененной строки

8

Я использую SQL Server 2008 Standard, который не имеет SEQUENCEфункции.

Внешняя система считывает данные из нескольких выделенных таблиц основной базы данных. Внешняя система хранит копию данных и периодически проверяет изменения данных и обновляет их копию.

Чтобы сделать синхронизацию эффективной, я хочу передать только те строки, которые были обновлены или вставлены после предыдущей синхронизации. (Строки никогда не удаляются). Чтобы узнать, какие строки были обновлены или вставлены с момента последней синхронизации, в каждой таблице есть bigintстолбец RowUpdateCounter.

Идея состоит в том, что всякий раз, когда строка вставляется или обновляется, число в ее 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 |
+----+-------+------------------+
  • RowUpdateCounterдля строк с идентификатором 1,2должен оставаться как есть, потому что эти строки не были изменены.
  • RowUpdateCounterдля строк с идентификатором 3,4должен измениться, потому что они были обновлены.
  • RowUpdateCounterдля строк с идентификатором 5,6должен измениться, потому что они были вставлены.
  • RowUpdateCounterдля всех измененных строк должно быть больше 4 (последняя RowUpdateCounterиз последовательности).

Порядок, в котором новые значения ( 5,6,7,8) назначаются измененным строкам, на самом деле не имеет значения. Новые значения могут иметь пробелы, например 15,26,47,58, но они никогда не должны уменьшаться.

В базе есть несколько таблиц с такими счетчиками. Не имеет значения, используют ли все они одну глобальную последовательность для своих номеров, или у каждой таблицы есть своя индивидуальная последовательность.


Я не хочу использовать столбец с отметкой даты и времени вместо целочисленного счетчика, потому что:

  • Часы на сервере могут прыгать как вперед, так и назад. Особенно, когда это на виртуальной машине.

  • Значения, возвращаемые системными функциями наподобие SYSDATETIMEодинаковы для всех затронутых строк. Процесс синхронизации должен иметь возможность считывать изменения в пакетах. Например, если размер пакета составляет 3 строки, то после выполнения MERGEшага выше процесс синхронизации будет читать только строки E,F,G. Когда процесс синхронизации будет запущен в следующий раз, он продолжится со строки H.


То, как я это делаю сейчас, довольно некрасиво.

Поскольку SEQUENCEв SQL Server 2008 его нет, я эмулирую его SEQUENCEс помощью специальной таблицы, IDENTITYкак показано в этом ответе . Это само по себе довольно уродливо и усугубляется тем фактом, что мне нужно генерировать не одну, а группу чисел за раз.

Затем у меня есть INSTEAD OF UPDATE, INSERTтриггер на каждой таблице с RowUpdateCounterи генерировать необходимые наборы чисел там.

В запросах INSERT, UPDATEand MERGEя устанавливаю RowUpdateCounterзначение 0, которое заменяется правильными значениями в триггере. В ???запросах выше 0.

Это работает, но есть ли более простое решение?

Владимир Баранов
источник
4
Не могли бы вы использовать версию строки / метку времени? Это двоичное поле, но значение будет меняться при каждом обновлении строки
Джеймс З
@JamesZ, мне нужно знать порядок, в котором были изменены строки. Процесс синхронизации считывает счетчик MAX из устаревшей копии таблицы, а затем знает, что нужно выбрать только те строки, у которых Counter больше этого значения. Это rowversionне дало бы мне такой возможности, если бы я правильно понял, что это такое.
Владимир Баранов
Спасибо @MartinSmith, я совсем забыл rowversion. Это выглядит очень заманчиво. Единственное, что меня беспокоит, - это то, что все примеры его использования, которые я видел до сих пор, вращаются вокруг определения, изменилась ли одна строка. Мне нужен эффективный способ узнать, какой набор строк изменился с определенного момента. Кроме того, возможно ли пропустить обновление?
Владимир Баранов
@MartinSmith time = 0: последнее значение версии строки, скажем, 122. time = 1: транзакция Aобновляет строку, ее версия строки изменяется на 123, Aеще не зафиксировано. time = 2: транзакция Bобновляет другую строку, ее версия строки изменяется на 124. time = 3: Bфиксирует. time = 4: процесс синхронизации запускается и извлекает все строки со значением rowversion> 122, что означает, что строки обновляются только с помощью B. время = 5: Aфиксирует Результат: изменения Aникогда не будут обнаружены процессом синхронизации. Я ошибаюсь? Может быть, какое-то умное использование MIN_ACTIVE_ROWVERSIONпоможет?
Владимир Баранов

Ответы:

5

Вы можете использовать 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()сравнение <=должно стать <и >стать >=.
Владимир Баранов
Согласно документам, существует существенная разница между активными незафиксированными транзакциями @@DBTSи MIN_ACTIVE_ROWVERSION()их наличием. Если приложение использует @@DBTSвместо MIN_ACTIVE_ROWVERSION, можно пропустить изменения, которые активны, когда происходит синхронизация.
Владимир Баранов
@VladimirBaranov - да, согласился, отредактировал.
Мартин Смит
-2

Вы пытались использовать IDENTITYопцию?

Например:

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

где

  • 1 -> Начальное значение
  • 2 -> каждая новая строка увеличивается на

Это похоже на ПОСЛЕДОВАТЕЛЬНОСТЬ в Oracle.

Бибхути Бхусан Падхи
источник
SQL Server не имеет никакой опции AUTOINCREMENT
Martin Smith
да. Поддерживается Access. SQL-сервер поддерживает опцию IDENTITY. Я обновил свой ответ выше. Спасибо !!
Бибхути Бхусан Падхи
4
IDENTITYне выполняет то, что требуется для автоматического увеличения как обновлений, так и вставок .
Мартин Смит
@BibhutiBhusanPadhi, мне нужно знать, какие строки были обновлены. Я не понимаю, как просто IDENTITYможет помочь.
Владимир Баранов