Estou preso em um problema de simultaneidade.
É um problema típico em que o usuário envia 2 ou 3 transações para persistir alguns dados que NÃO DEVEM SER duplicados no banco de dados, no caso de um registro duplicado, você deve retornar um erro.
Esse problema é fácil quando você pode adicionar um índice (exclusivo) a uma coluna onde você armazena um hash.
Mas, neste caso, eu tenho uma tabela enorme (provavelmente milhões de registros) e não posso simplesmente modificar a tabela.
De fato, temos uma coluna na qual armazenamos um hash dos dados que não devem ser duplicados, mas um índice exclusivo não foi definido.
Estou tentando no meu código java verificar se existe um pouco antes do flush, ainda recebendo duplicatas.
Minhas soluções possíveis para isso são:
- Crie um gatilho que verifique se o hash que estou tentando inserir já existe na tabela.
- Crie outra tabela para armazenar índices exclusivos para esta tabela e adicione uma chave estrangeira à tabela principal.
- Sente-se na posição fetal e chore
fonte
Respostas:
Existem alguns cenários possíveis que são fáceis de resolver e um cenário pernicioso que não é.
Para um usuário que insere um valor, insere o mesmo valor algum tempo depois, um simples SELECT antes que o INSERT detecte o problema. Isso funciona para o caso em que um usuário envia um valor e, algum tempo depois, outro usuário envia o mesmo valor.
Se o usuário enviar uma lista de valores com duplicatas - por exemplo, {ABC, DEF, ABC} - em uma única chamada do código, o aplicativo poderá detectar e filtrar as duplicatas, talvez causando um erro. Você também precisará verificar se o banco de dados não contém nenhum valor exclusivo antes da inserção.
O cenário complicado é quando a gravação de um usuário está dentro do DBMS ao mesmo tempo que a gravação de outro usuário e ele está gravando o mesmo valor. Então você tem uma corrida uma condição entre eles. Como o DBMS é (provavelmente - você não diz qual deles está usando) um sistema multitarefa preemptivo, qualquer tarefa pode ser pausada a qualquer momento de sua execução. Isso significa que a tarefa do usuário1 pode verificar se não há linha existente, a tarefa do usuário2 pode verificar se não há linha existente, a tarefa do usuário1 pode inserir essa linha e a tarefa do usuário2 pode inserir essa linha. Em cada momento, as tarefas são felizes individualmente e estão fazendo a coisa certa. Globalmente, no entanto, ocorre um erro.
Normalmente, um DBMS lidaria com isso colocando um bloqueio no valor em questão. Nesse problema, você está criando uma nova linha para que ainda não haja nada a ser bloqueado. A resposta é um bloqueio de intervalo. Como sugere, isso bloqueia uma faixa de valores, existindo ou não no momento. Uma vez bloqueado, esse intervalo não pode ser acessado por outra tarefa até que o bloqueio seja liberado. Para obter bloqueios de intervalo, você deve especificar e nível de isolamento SERIALIZABLE . O fenômeno de outra tarefa se esgueirar seguidamente após a verificação da tarefa é conhecido como registros fantasmas .
Definir o nível de isolamento como Serializable em todo o aplicativo terá implicações. O rendimento será reduzido. Outras condições de corrida que funcionaram bem o suficiente no passado podem começar a mostrar erros agora. Eu sugeriria defini-lo na conexão que executa seu código de indução de duplicado e deixar o restante do aplicativo como está.
Uma alternativa baseada em código é verificar depois da gravação e não antes. O mesmo acontece com INSERT e, em seguida, conte o número de linhas que possuem esse valor de hash. Se houver duplicados, reverter a ação. Isso pode ter alguns resultados perversos. Diga que a tarefa 1 grava e depois a tarefa 2. Em seguida, a tarefa 1 verifica e encontra uma duplicata. Ele retrocede, embora tenha sido o primeiro. Da mesma forma, ambas as tarefas podem detectar a duplicação e a reversão. Mas pelo menos você terá uma mensagem para trabalhar, um mecanismo de nova tentativa e nenhuma nova duplicata. As reversões são desaprovadas, como usar exceções para controlar o fluxo do programa. Note bem que todoso trabalho na transação será revertido, não apenas a gravação de indução de duplicação. E você precisará ter transações explícitas que podem reduzir a simultaneidade. A verificação duplicada será terrivelmente lenta, a menos que você tenha um índice no hash. Se você o fizer, poderá torná-lo único!
Como você comentou, a solução real é um índice exclusivo. Parece-me que isso deve caber na sua janela de manutenção (apesar de você conhecer melhor o seu sistema). Digamos que o hash tenha oito bytes. Para cem milhões de linhas, são cerca de 1 GB. A experiência sugere que um pouco de hardware processaria essas muitas linhas em um ou dois minutos, no máximo. A verificação e a eliminação duplicadas serão adicionadas a isso, mas podem ser scripts com antecedência. Este é apenas um aparte, no entanto.
fonte
A verificação de colisões de hash é um bom primeiro passo, mas cuidado, você não pode garantir que o mesmo programa produza o mesmo hash nos mesmos dados se for reiniciado . Muitas funções hash "rápidas" usam uma impressão embutida que é propagada no início do programa. Use um hash criptográfico se o hash precisar ser sempre o mesmo, não importa como, neste aplicativo. Observe que você não precisa de um hash criptográfico bom ou seguro.
O segundo passo é realmente verificar a igualdade de dados, pois mesmo as melhores funções de hash às vezes resultam em colisões, pois você está (geralmente) reduzindo a entropia de seus dados.
Então:
Etapa 1: verifique se há uma colisão em um hash criptográfico
Etapa 2: se os hashes corresponderem, verifique se os dados reais são os mesmos
fonte
Crie uma nova tabela com uma chave primária exclusiva
No lado do cliente, comece a gerar GUIDs para cada registro, para que você possa detectar reenvios simples.
Coloque novos registros na nova tabela para que, pelo menos, você seja bom para receber novos dados.
Tenha uma coluna na nova tabela "CheckedAgainstOldData"
Tenha uma tarefa de back-end que faça o que você faz com a verificação lenta lenta de hash para verificar se ele pode encontrar uma duplicata nos dados antigos e definir o sinalizador de acordo, rejeite as duplicatas nesse momento, enviando uma notificação de volta ao cliente.
Enquanto isso, tenha outra tarefa de back-end que move os dados da tabela antiga para a nova, verificando se há duplicatas na verificação de hash e gerando o GUID.
Você pode deixar essa tarefa em execução por vários dias (se necessário), transferindo os dados sem tempo de inatividade.
Após a conclusão da transferência, você pode desativar o processo lento "CheckedAgainstOldData". e transfira todos os dados para uma única tabela.
Francamente, se o problema for tão ruim quanto o descrito e o software for antigo, você terá milhares de duplicatas.
fonte
Supondo que os dados provenientes do "usuário" signifiquem alguém sentado em um teclado e que os enganos surjam de dois usuários inserindo os mesmos dados no mesmo momento. Tente adicionar uma função que causa um atraso aleatório no início do gatilho. Dê um mínimo do tempo que for necessário para gravar um novo registro na tabela e provavelmente um máximo de não mais do que um nanocentro. Dessa forma, quando você recebe solicitações fraudulentas, a primeira deve ser feita e o gatilho de existência deve retroceder o resultado correto. (Esclarecimento: cada chamada deve ter seu próprio tempo de atraso aleatório exclusivo, seguindo os mesmos princípios do protocolo ALOHA )
fonte