Ao atualizar uma linha em uma tabela temporal, os valores antigos da linha são armazenados na tabela de histórico com a hora de início da transação como a SysEndTime
. Os novos valores na tabela atual terão a hora de início da transação como o SysStartTime
.
SysStartTime
e SysEndTime
são datetime2
colunas usadas por tabelas temporais para registrar quando uma linha era a versão atual. Hora de início da transação é o horário em que a transação que contém as atualizações foi iniciada.
BOL diz:
Os horários registrados nas colunas datetime2 do sistema são baseados no horário de início da transação. Por exemplo, todas as linhas inseridas em uma única transação terão o mesmo horário UTC registrado na coluna correspondente ao início do período SYSTEM_TIME.
Exemplo: começo a atualizar todas as linhas da minha tabela Pedidos em 20160707 11:00:00
e a transação leva 5 minutos para ser executada. Isso cria uma linha na tabela de histórico para cada linha com SysEndTime
como 20160707 11:00:00
. Todas as linhas na tabela atual terão um SysStartTime
de 20160707 11:00:00
.
Se alguém executasse uma consulta em 20160707 11:01:00
(enquanto a atualização estiver em execução), veria os valores antigos (assumindo o nível de isolamento confirmado de leitura padrão).
Mas se alguém usasse a AS OF
sintaxe para consultar a tabela temporal como estava, 20160707 11:01:00
eles veriam os novos valores porque eles SysStartTime
seriam 20160707 11:00:00
.
Para mim, isso significa que não mostra essas linhas como estavam naquele momento. Se ele usasse o horário de término da transação, o problema não existiria.
Perguntas: É por design? Estou esquecendo de algo?
A única razão pela qual posso pensar que está usando a hora de início da transação é que ela é a única 'conhecida' quando a transação é iniciada. Ele não sabe quando a transação será encerrada quando for iniciada e levaria tempo para aplicar a hora final no final, o que invalidaria o horário final em que estava sendo aplicada. Isso faz sentido?
Isso deve permitir que você recrie o problema.
fonte
20160707 11:04:58
e agora você atualiza todas as linhas com esse carimbo de data / hora. Mas essa atualização também é executada por alguns segundos e termina às20160707 11:05:02
, agora, qual carimbo de data e hora é o final correto da transação? Ou suponha que você usouRead Uncommited
at20160707 11:05:00
e tenha retornado linhas, mas depoisAS OF
não as mostra.Respostas:
A idéia é acompanhar o tempo lógico versus o físico. Lógico simplesmente se refere ao que um usuário / aplicativo espera que seja o tempo de uma inserção / atualização / exclusão. O fato de a operação DML demorar um pouco, por qualquer motivo, não é significativo ou mesmo facilmente determinado e compreendido por um usuário. Se você já teve que explicar a contenção de bloqueio x trava para um contador (eu tenho), é uma situação comparável.
Por exemplo, quando Bob "diz" ao aplicativo que todos os funcionários do departamento de Bob começarão a ganhar US $ 42 / min às
20160707 11:00:00
, Bob (e seus funcionários) espera que o pagamento de todos agora seja calculado em US $ 42 / min a partir desse momento. Bob não se importa que, para que isso ocorra, o aplicativo precisa fazer 2 leituras e 6 gravações no banco de dados por funcionário, e seus arquivos de dados e log ficam em um monte de unidades RAID-5 SATA II, o que leva cerca de 7 minutos. para concluir a tarefa para todos os 256 funcionários de Bob. Bob, seu contador e o gerente da folha de pagamento cuidam para que todos os seus funcionários recebam US $ 42 / min a partir20160707 11:00:00
. Senão, os funcionários que foram atualizados20160707 11:00:01
serão levemente aborrecidos, enquanto aqueles cujos registros foram atualizados20160707 11:00:07
estarão reunidos fora do departamento de folha de pagamento.Existem casos de uso válidos para rastrear o tempo físico, como depuração e análise forense, mas, para o usuário final, geralmente não faz sentido. O Tlog mantém as informações de pedido e de tempo de cada uma das operações de gravação (entre outras coisas), para que esteja lá se você souber como procurar.
fonte
Acredito que essa seja realmente uma falha de design, embora não seja específica do SQL Server 2016, pois todas as outras implementações existentes de tabelas temporais (tanto quanto eu sei) têm a mesma falha. Os problemas que podem surgir com as tabelas temporais por causa disso são bastante graves; o cenário no seu exemplo é moderado em comparação com o que pode dar errado em geral:
Referências de chave estrangeira quebradas : suponha que tenhamos duas tabelas temporais, com a tabela A tendo uma referência de chave estrangeira à tabela B. Agora, digamos que temos duas transações, ambas em execução no nível de isolamento READ COMMITTED: a transação 1 começa antes da transação 2, transação 2 insere uma linha na tabela B e confirma, a transação 1 insere uma linha na tabela A com uma referência à linha recém-adicionada de B. Como a adição da nova linha a B já foi confirmada, a restrição de chave estrangeira é satisfeita e a transação 1 é capaz de confirmar com sucesso. No entanto, se visualizarmos o banco de dados "AS OF" em algum momento entre o início da transação 1 e o início da transação 2, veremos a tabela A com uma referência a uma linha de B que não existe. Então, neste caso,a tabela temporal fornece uma visão inconsistente do banco de dados . Obviamente, essa não era a intenção do padrão SQL: 2011, que afirma,
Chaves primárias não exclusivas : digamos que temos uma tabela com uma chave primária e duas transações, ambas no nível de isolamento READ COMMITTED, no qual ocorre o seguinte: Após o início da transação 1, mas antes de tocar nessa tabela, a transação 2 exclui um determinado linha da tabela e confirma. Em seguida, a transação 1 insere uma nova linha com a mesma chave primária que a que foi excluída. Isso ocorre muito bem, mas quando você olha para a tabela A PARTIR DE ENTREGAMENTE no momento em que a transação 1 começou e quando a transação 2 começou, veremos duas linhas com a mesma chave primária.
Erros em atualizações simultâneas : digamos que temos uma tabela e duas transações que atualizam a mesma linha nela, novamente em um nível de isolamento READ COMMITTED. A transação 1 começa primeiro, mas a transação 2 é a primeira a atualizar a linha. A transação 2 é confirmada e a transação 1 faz uma atualização diferente na linha e confirmada. Tudo bem, exceto que, se for uma tabela temporal, após a execução da atualização na transação 1, quando o sistema inserir a linha necessária na tabela de histórico, o SysStartTime gerado será o horário de início da transação 2, enquanto o SysEndTime será o horário de início da transação 1, que não é um intervalo de tempo válido, pois o SysEndTime seria anterior ao SysStartTime. Nesse caso, o SQL Server gera um erro e reverte a transação (por exemplo, consulteesta discussão ). Isso é muito desagradável, pois no nível de isolamento READ COMMITTED, não seria de esperar que os problemas de concorrência levassem a falhas definitivas, o que significa que os aplicativos não necessariamente serão preparados para fazer novas tentativas. Em particular, isso é contrário a uma "garantia" na documentação da Microsoft:
Outras implementações de tabelas temporais lidaram com esse cenário (duas transações simultâneas atualizando a mesma linha) oferecendo uma opção para "ajustar" automaticamente os carimbos de data e hora, se eles forem inválidos (veja aqui e aqui ). Essa é uma solução feia, pois tem a conseqüência infeliz de interromper a atomicidade das transações, pois outras instruções nas mesmas transações geralmente não terão seus timestamps ajustados da mesma maneira; isto é, com esta solução alternativa, se visualizarmos o banco de dados "AS OF" em determinados momentos, poderemos ver transações parcialmente executadas.
Solução: Você já sugeriu a solução óbvia, que é a implementação de usar o horário de término da transação (ou seja, o horário de confirmação) em vez do horário de início. Sim, é verdade que, quando estamos executando uma instrução no meio de uma transação, é impossível saber qual será o tempo de consolidação (como é no futuro, ou talvez nem exista se a transação for rolada). de volta). Mas isso não significa que a solução não é implementável; só tem que ser feito de uma maneira diferente. Por exemplo, ao executar uma instrução UPDATE ou DELETE, ao criar a linha do histórico, o sistema pode simplesmente inserir o ID da transação atual em vez de uma hora de início e, em seguida, o ID pode ser convertido em um carimbo de data / hora posteriormente pelo sistema após a transação ser confirmada .
No contexto desse tipo de implementação, eu sugeriria que, antes da transação ser confirmada, todas as linhas adicionadas à tabela de histórico não deveriam ser visíveis ao usuário. Da perspectiva do usuário, deve parecer simplesmente que essas linhas foram adicionadas (com o carimbo de data / hora de confirmação) no momento da confirmação. Em particular, se a transação nunca for confirmada com êxito, nunca deverá aparecer no histórico. Obviamente, isso é inconsistente com o padrão SQL: 2011, que descreve as inserções no histórico (incluindo registros de data e hora) como ocorrendo no momento das instruções UPDATE e DELETE (em oposição ao horário da confirmação). Mas não acho que isso realmente importe, considerando que o padrão nunca foi implementado adequadamente (e possivelmente nunca pode ser) devido aos problemas descritos acima,
Do ponto de vista do desempenho, pode parecer indesejável que o sistema precise voltar e revisitar as linhas do histórico para preencher o carimbo de data / hora de confirmação. Mas, dependendo de como isso é feito, o custo pode ser bastante baixo. Não estou muito familiarizado com o funcionamento interno do SQL Server, mas o PostgreSQL, por exemplo, usa um write-ahead-log, o que faz com que, se várias atualizações forem executadas nas mesmas partes de uma tabela, essas atualizações sejam consolidadas para que o os dados precisam ser gravados apenas uma vez nas páginas da tabela física - e isso normalmente se aplica a esse cenário. Em qualquer caso,
É claro que, desde que (até onde eu sei) esse tipo de sistema nunca foi implementado, não posso dizer com certeza que funcionaria - talvez haja algo que esteja faltando - mas não vejo nenhuma razão por que não funcionou.
fonte
No momento em que você confirma sua transação, todos os dados devem ser gravados dentro das páginas de dados (na memória e no disco no arquivo de log). Incluindo
SysStartTime
eSysEndTime
colunas. Como você pode saber o horário de término da transação antes que ela seja realmente concluída?A menos que você possa prever o futuro, usar a hora de início da transação é a única opção, mesmo que seja menos intuitiva.
fonte