Estou usando o SQL Server 2008 Standard, que não possui um SEQUENCE
recurso.
Um sistema externo lê dados de várias tabelas dedicadas do banco de dados principal. O sistema externo mantém uma cópia dos dados e verifica periodicamente as alterações nos dados e atualiza sua cópia.
Para tornar a sincronização eficiente, desejo transferir apenas linhas que foram atualizadas ou inseridas desde a sincronização anterior. (As linhas nunca são excluídas). Para saber quais linhas foram atualizadas ou inseridas desde a última sincronização, há uma bigint
coluna RowUpdateCounter
em cada tabela.
A ideia é que sempre que uma linha for inserida ou atualizada, o número em sua RowUpdateCounter
coluna mudará. Os valores que entram na RowUpdateCounter
coluna devem ser obtidos de uma sequência cada vez maior de números. Os valores na RowUpdateCounter
coluna devem ser exclusivos e cada novo valor armazenado em uma tabela deve ser maior que qualquer valor anterior.
Por favor, veja os scripts que mostram o comportamento desejado.
Esquema
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
INSERIR algumas linhas
INSERT INTO [dbo].[Test]
([ID]
,[Value]
,[RowUpdateCounter])
VALUES
(1, 'A', ???),
(2, 'B', ???),
(3, 'C', ???),
(4, 'D', ???);
Resultado esperado
+----+-------+------------------+
| ID | Value | RowUpdateCounter |
+----+-------+------------------+
| 1 | A | 1 |
| 2 | B | 2 |
| 3 | C | 3 |
| 4 | D | 4 |
+----+-------+------------------+
Os valores gerados em RowUpdateCounter
podem ser diferentes, digamos 5, 3, 7, 9
,. Eles devem ser únicos e maiores que 0, desde que começamos da tabela vazia.
INSERT e UPDATE algumas linhas
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
,???)
;
Resultado esperado
+----+-------+------------------+
| ID | Value | RowUpdateCounter |
+----+-------+------------------+
| 1 | A | 1 |
| 2 | B | 2 |
| 3 | E | 5 |
| 4 | F | 6 |
| 5 | G | 7 |
| 6 | H | 8 |
+----+-------+------------------+
RowUpdateCounter
para linhas com ID1,2
deve permanecer como está, porque essas linhas não foram alteradas.RowUpdateCounter
para linhas com ID3,4
deve mudar, porque foram atualizadas.RowUpdateCounter
para linhas com ID5,6
deve mudar, porque foram inseridas.RowUpdateCounter
para todas as linhas alteradas deve ser maior que 4 (a últimaRowUpdateCounter
da sequência).
A ordem na qual novos valores ( 5,6,7,8
) são atribuídos a linhas alteradas não importa realmente. Os novos valores podem ter lacunas, por exemplo 15,26,47,58
, mas nunca devem diminuir.
Existem várias tabelas com esses contadores no banco de dados. Não importa se todos eles usam a única seqüência global para seus números ou se cada tabela possui sua própria sequência individual.
Não quero usar uma coluna com um carimbo de data e hora em vez de um contador inteiro, porque:
O relógio no servidor pode pular para frente e para trás. Especialmente quando está em uma máquina virtual.
Os valores retornados pelas funções do sistema
SYSDATETIME
são iguais para todas as linhas afetadas. O processo de sincronização deve poder ler as alterações nos lotes. Por exemplo, se o tamanho do lote for de 3 linhas, após aMERGE
etapa acima, o processo de sincronização lerá apenas as linhasE,F,G
. Quando o processo de sincronização for executado na próxima vez, continuará da linhaH
.
O jeito que estou fazendo isso agora é feio.
Como não existe SEQUENCE
no SQL Server 2008, eu emulo o SEQUENCE
por uma tabela dedicada, IDENTITY
conforme mostrado nesta resposta . Isso por si só é bastante feio e exacerbado pelo fato de eu precisar gerar não apenas um, mas um lote de números de uma só vez.
Então, eu tenho um INSTEAD OF UPDATE, INSERT
gatilho em cada tabela com RowUpdateCounter
oe gerar conjuntos necessários de números lá.
Nas consultas e INSERT
, defino como 0, que é substituído pelos valores corretos no gatilho. As consultas acima são .UPDATE
MERGE
RowUpdateCounter
???
0
Funciona, mas existe uma solução mais fácil?
fonte
rowversion
não iria me dar essa possibilidade, se eu entendi corretamente o que é ... É garantida a ser cada vez maior?rowversion
. Parece muito tentador. Minha única preocupação é que todos os exemplos de uso que eu vi até agora giram em torno de detectar se uma única linha foi alterada. Preciso de uma maneira eficiente de saber que conjunto de linhas mudou desde um determinado momento. Além disso, é possível perder uma atualização?A
atualiza uma linha, sua versão de linha é alterada para 123,A
ainda não foi confirmada. time = 2: a transaçãoB
atualiza outra linha, sua versão de linha muda para 124. time = 3:B
confirma. time = 4: o processo de sincronização é executado e busca todas as linhas com versão> 122, o que significa que as linhas são atualizadas apenas porB
. time = 5:A
confirma. Resultado: as alterações deA
nunca serão captadas pelo processo de sincronização. Estou errado? Talvez algum uso inteligente deMIN_ACTIVE_ROWVERSION
ajude?Respostas:
Você pode usar uma
ROWVERSION
coluna para isso.A documentação afirma que
Os valores são
BINARY(8)
e você deve considerá-los comoBINARY
antesBIGINT
e depois de0x7FFFFFFFFFFFFFFF
prosseguir0x80...
e começar a trabalhar se-9223372036854775808
for tratado como assinadobigint
.Um exemplo completo está abaixo. Manter o índice na
ROWVERSION
coluna será caro se você tiver muitas atualizações, portanto, você pode testar sua carga de trabalho com e sem para ver se vale o custo.fonte
@@DBTS
, deveria haverMIN_ACTIVE_ROWVERSION()
, e se o uso daMIN_ACTIVE_ROWVERSION()
comparação<=
deveria se tornar<
e>
se tornar>=
.@@DBTS
eMIN_ACTIVE_ROWVERSION()
se há transações não confirmadas ativas. Se um aplicativo usar em@@DBTS
vez deMIN_ACTIVE_ROWVERSION
, é possível perder as alterações ativas quando a sincronização ocorrer.Você já tentou usar a
IDENTITY
opção?Por exemplo:
Onde
Isso é semelhante ao SEQUENCE no Oracle.
fonte
IDENTITY
não faz o necessário para incrementar automaticamente as atualizações e as inserções .IDENTITY
pode ajudar.