Meu projeto é auditar de 5 a 10 tabelas existentes em nosso sistema sem prolongar as transações. Seja qual for o método usado, ele deve funcionar no SQL Server Express 2005 até (eventualmente) 2016.
Eu fiz pesquisas sobre Change Data Capture (CDC) e Change Tracking. O controle de alterações não captura as alterações específicas e o CDC está disponível apenas na Enterprise Edition.
Então me deparei com o Service Broker. Fiquei intrigado com o Service Broker, então comecei a criar um protótipo. O Service Broker está funcionando bem, mas as respostas recebidas em duas de minhas outras postagens me levam a acreditar que esse pode não ser o caminho certo a seguir. Muito complicado para nada. Ainda estou no estágio de análise e tentando coisas diferentes como parte da minha análise.
No momento, os resultados do corretor de serviços não são convincentes... uma atualização em massa de 105.000 itens para uma tabela de preços leva 38 segundos, enquanto o processamento da Fila (a parte de auditoria) leva 17 segundos... mas os 38 segundos incluem o processamento duplo de inserção em 2 tabelas #temp que são usadas para inserir nos TMPins e TMPdel. Então, acho que posso cortar isso pela metade... Agora estou questionando o uso do service broker... logicamente, o gatilho provavelmente levaria o mesmo tempo apenas inserindo as informações diretamente na tabela de auditoria. .
Para esclarecer, quando digo inserção em massa, não é a função "Inserção em massa". Estou falando de um grande bloco de dados que é inserido ou atualizado de uma só vez. ao atualizar 105.000 itens na tabela de preços, quero auditar as alterações ocorridas. Quando digo mudanças que acontecem, resolvi inserir os novos valores na tabela de auditoria (se for um insert ou update) ou inserir a chave primária com todos os outros campos nulos (para registros que foram apagados)... Então sim! pode ser depois que os dados forem carregados, mas não quero perder nenhuma auditoria (não quero que uma transação passe fora de ordem)
As outras duas postagens ajudarão a obter o contexto do que estou tentando fazer e do que tentei:
- Gatilho para criar uma tabela de variáveis a ser enviada ao service broker
- gatilhos - usando as tabelas inseridas/excluídas no SQL dinâmico
Eu valorizo cada ideia.
Suponho que a postagem a seguir seja a base do que você está usando atualmente: Auditoria assíncrona centralizada com o Service Broker .
Embora eu realmente goste do Service Broker, não acho que seja o mais adequado para lidar com essa situação específica. As principais preocupações que tenho com o Service Broker, pelo menos neste cenário específico, são:
INSERTED
e no gatilho DML.DELETED
Minha preferência é despejar as alterações em uma tabela de filas e, em seguida, em um processo separado programado para ser executado a cada X minutos, ler o número Y de linhas e processá-las.
Crie uma tabela de filas para armazenar os dados de auditoria de uma tabela específica (não para cada operação DML individual). A tabela deve ter:
LOCK_ESCALATION
opção definida comoDISABLE
(viaALTER TABLE {name} SET (LOCK_ESCALATION = DISABLE)
) para evitar conflito entre o registro de novos dados pelo gatilho e a remoção de dados do processamento de auditoria. Esta opção foi introduzida no SQL Server 2008, portanto não pode ser usada em instâncias de 2005, mas não há razão para não usá-la nas instâncias de 2008 e mais recentes, pois não altera a funcionalidade em nenhum dos casos.AuditID
PK que é uma das seguintes:INT IDENTITY
partir de -2147483648BIGINT IDENTITY
INT
com seu valor vindo de aSEQUENCE
que é definido comoCYCLE
INSERTED
tabela, pois os valores naDELETED
tabela já devem estar em seus dados de auditoria anteriores.Crie um gatilho que simplesmente insere na tabela de filas. Se você precisar passar valores "antigos" e "novos", JUNTE as tabelas
INSERTED
eDELETED
aqui em vez de tentar manter duas tabelas de filas separadas, uma para cada uma dessas pseudotabelas. Juntá-los neste ponto e inserir os valores "antigo" e "novo" em uma única linha é um pequeno impacto no desempenho, mas garantirá que cada operação permaneça unida e em ordem cronológica (via PK de incremento).Se você não estiver rastreando as alterações em todos os campos, use UPDATE() ou COLUMNS_UPDATED() para determinar se as colunas que estão sendo auditadas foram realmente atualizadas (para
UPDATE
operações; essas funções retornam true para todas as colunas emINSERT
operações). Lembre-se de que as funçõesUPDATE()
e não determinam se uma alteração foi feita no valor da(s) coluna(s)!! Eles informam apenas se as colunas estavam presentes na cláusula da declaração. A única maneira de determinar se o(s) valor(es) realmente mudou(s) é JOIN the eCOLUMNS_UPDATED()
SET
UPDATE
INSERTED
DELETED
tabelas. Mas, se não estiver rastreando todas as colunas, essas funções são ótimas para sair do Gatilho sem fazer nenhum trabalho se nenhuma coluna rastreada tiver sido alterada. Ou seja, no início do Trigger, você faria:Se você não estiver capturando valores "antigos" e "novos" na tabela de filas, essa é a única oportunidade de eliminar registros "atualizados" em que nenhuma coluna foi realmente alterada. Ao inserir na tabela de filas, basta filtrar
WHERE IntColOld <> IntColNew OR StringFieldOld <> StringFieldNew COLLATE Latin1_General_BIN2 OR ISNULL(DateFieldOld, '1900-01-01') <> ISNULL(DateFieldNew, '1900-01-01') OR ...
. É importante usar um_BIN2
agrupamento em campos de string para garantir que os dois campos sejam de fato idênticos. E você precisa usarINSULL
apenas os campos anuláveis para que eles possam ser iguais se ambos foremNULL
.Crie um procedimento armazenado que será executado por X segundos e, nesse período, processará conjuntos de
TOP(@BatchSize)
linhas, usandoORDER BY AuditID ASC
. Você faz isso por meio de umWHILE
loop que verifica qual foi definidoGETDATE()
no@StartTime
início do procedimento armazenado. Dependendo do processamento que você precisa fazer, às vezes é mais fácil criar uma tabela temporária local paraINSERT INTO #TempTable SELECT...
o conjunto de trabalho (então você teráDELETE
essas linhas no final de cada loop) ouDELETE FROM
usarOUTPUT INTO #TempTable
.Aqui você pode remover modificações duplicadas. Se você estiver rastreando valores "antigos" e "novos" para cada linha, também deverá eliminar as linhas em que nenhuma das colunas realmente foi alterada. Você pode fazer isso testando
WHERE IntColOld = IntColNew AND StringFieldOld = StringFieldNew COLLATE Latin1_General_BIN2 AND ...
. É importante usar um_BIN2
agrupamento em campos de string para garantir que os dois campos sejam de fato idênticos. Os agrupamentos não binários, mesmo se diferenciam maiúsculas de minúsculas e acentos, etc., ainda permitem normalizações linguísticas que devem comparar o mesmo para comparações regulares, mas não durante a auditoria. Não use_BIN
agrupamentos, pois eles foram obsoletos desde o SQL Server 2005, que foi quando os_BIN2
agrupamentos foram lançados.Tudo feito dentro do loop precisa estar dentro de um //
BEGIN TRAN
explícito . Use um bloco TRY / CATCH para gerenciar isso. Isso evitará a perda de alguns registros ou o processamento duplo de alguns.COMMIT
ROLLBACK
Agende o procedimento armazenado para ser executado a cada X minutos. Normalmente, isso é feito por meio de um trabalho do SQL Server Agent , mas o SQL Server Agent não está disponível para as edições Express. Nesse caso, você pode usar o Agendador de Tarefas do Windows ou obter algo como Quartz.NET .
A configuração de um processo dessa maneira permite que você tenha um processo mais consistente e ajustável que deve processar constantemente X registros a cada Y minutos. Portanto, não importa se você tem 1 milhão de operações DML de linha única ou uma única operação DML de 1 milhão de linhas; esse processo continuará avançando, fazendo o que faz.