9.5 e mais recente:
PostgreSQL 9.5 e suporte mais recente INSERT ... ON CONFLICT UPDATE
(e ON CONFLICT DO NOTHING
), ou seja, upsert.
Comparação comON DUPLICATE KEY UPDATE
.
Explicação rápida .
Para uso, consulte o manual - especificamente a cláusula conflito_ação no diagrama de sintaxe e o texto explicativo .
Ao contrário das soluções para a 9.4 e anteriores fornecidas abaixo, esse recurso funciona com várias linhas conflitantes e não requer bloqueio exclusivo ou loop de repetição.
O commit adicionando o recurso está aqui e a discussão sobre seu desenvolvimento está aqui .
Se você está na versão 9.5 e não precisa ser compatível com versões anteriores, pode parar de ler agora .
9.4 e mais velhos:
O PostgreSQL não tem nenhum built-in UPSERT
MERGE
recurso interno (ou ) e é muito difícil fazê-lo de maneira eficiente em face do uso simultâneo.
Este artigo discute o problema em detalhes úteis .
Em geral, você deve escolher entre duas opções:
- Operações individuais de inserção / atualização em um loop de repetição; ou
- Bloqueando a tabela e fazendo a mesclagem em lote
Loop de repetição de linha individual
O uso de upserts de linha individuais em um loop de repetição é a opção razoável se você desejar várias conexões simultaneamente tentando executar inserções.
A documentação do PostgreSQL contém um procedimento útil que permite fazer isso em um loop dentro do banco de dados . Ele protege contra atualizações perdidas e insere corridas, ao contrário das soluções mais ingênuas. Funcionará apenas emREAD COMMITTED
modo e só será seguro se for a única coisa que você fizer na transação. A função não funcionará corretamente se acionadores ou chaves exclusivas secundárias causarem violações exclusivas.
Essa estratégia é muito ineficiente. Sempre que possível, você deve enfileirar o trabalho e fazer uma upsert em massa, conforme descrito abaixo.
Muitas soluções tentadas para esse problema não consideram reversões, portanto resultam em atualizações incompletas. Duas transações competem entre si; um deles com sucesso INSERT
s; o outro recebe um erro de chave duplicada e, em UPDATE
vez disso, executa um . Os UPDATE
blocos aguardando INSERT
a reversão ou confirmação. Quando é revertida, a UPDATE
nova verificação da condição corresponde a zero linhas, mesmo que as UPDATE
confirmações não tenham feito o upsert esperado. Você deve verificar as contagens da linha de resultados e tentar novamente quando necessário.
Algumas soluções tentadas também não consideram as corridas SELECT. Se você tentar o óbvio e simples:
-- THIS IS WRONG. DO NOT COPY IT. It's an EXAMPLE.
BEGIN;
UPDATE testtable
SET somedata = 'blah'
WHERE id = 2;
-- Remember, this is WRONG. Do NOT COPY IT.
INSERT INTO testtable (id, somedata)
SELECT 2, 'blah'
WHERE NOT EXISTS (SELECT 1 FROM testtable WHERE testtable.id = 2);
COMMIT;
então, quando dois executam ao mesmo tempo, existem vários modos de falha. Uma é a questão já discutida com uma nova verificação de atualização. Outro é onde ambos UPDATE
ao mesmo tempo, combinando zero linhas e continuando. Em seguida, ambos fazem o EXISTS
teste, o que acontece antes do INSERT
. Ambos têm zero linhas, e ambos fazem o INSERT
. Um falha com um erro de chave duplicada.
É por isso que você precisa de um loop de repetição. Você pode pensar que pode evitar erros de chave duplicados ou atualizações perdidas com o SQL inteligente, mas não pode. Você precisa verificar a contagem de linhas ou manipular erros de chave duplicados (dependendo da abordagem escolhida) e tentar novamente.
Por favor, não role sua própria solução para isso. Como na fila de mensagens, provavelmente está errado.
Upsert a granel com trava
Às vezes, você deseja fazer uma upsert em massa, em que possui um novo conjunto de dados que deseja mesclar em um conjunto de dados existente mais antigo. Isso é muito mais eficiente do que upserts individuais de linha e deve ser preferido sempre que possível.
Nesse caso, você normalmente segue o seguinte processo:
CREATE
uma TEMPORARY
mesa
COPY
ou insira em massa os novos dados na tabela temporária
LOCK
a tabela de destino IN EXCLUSIVE MODE
. Isso permite que outras transações SELECT
, mas não façam alterações na tabela.
Faça um UPDATE ... FROM
dos registros existentes usando os valores na tabela temporária;
Faça uma INSERT
das linhas que ainda não existem na tabela de destino;
COMMIT
, liberando a trava.
Por exemplo, para o exemplo fornecido na pergunta, usando valores múltiplos INSERT
para preencher a tabela temporária:
BEGIN;
CREATE TEMPORARY TABLE newvals(id integer, somedata text);
INSERT INTO newvals(id, somedata) VALUES (2, 'Joe'), (3, 'Alan');
LOCK TABLE testtable IN EXCLUSIVE MODE;
UPDATE testtable
SET somedata = newvals.somedata
FROM newvals
WHERE newvals.id = testtable.id;
INSERT INTO testtable
SELECT newvals.id, newvals.somedata
FROM newvals
LEFT OUTER JOIN testtable ON (testtable.id = newvals.id)
WHERE testtable.id IS NULL;
COMMIT;
Leitura relacionada
A respeito MERGE
?
Padrão SQL MERGE
Na verdade, o possui semânticas de concorrência mal definidas e não é adequado para upserting sem bloquear uma tabela primeiro.
É uma instrução OLAP realmente útil para mesclagem de dados, mas na verdade não é uma solução útil para upsert com segurança de simultaneidade. Há muitos conselhos para as pessoas que usam outros DBMSes para usarMERGE
DBMSes para upserts, mas na verdade está errado.
Outros bancos de dados: