"INSERIR IGNORAR" vs "INSERIR ... NA DUPLICAÇÃO DA CHAVE DE ATUALIZAÇÃO"

833

Ao executar uma INSERTinstrução com muitas linhas, desejo pular entradas duplicadas que, de outra forma, causariam falhas. Após algumas pesquisas, minhas opções parecem ser o uso de:

  • ON DUPLICATE KEY UPDATE o que implica uma atualização desnecessária a algum custo, ou
  • INSERT IGNORE o que implica um convite para que outros tipos de falha não sejam anunciados.

Estou certo nessas suposições? Qual é a melhor maneira de simplesmente pular as linhas que podem causar duplicatas e continuar nas outras linhas?

Thomas G Henry
fonte

Respostas:

991

Eu recomendaria usar INSERT...ON DUPLICATE KEY UPDATE.

Se você usar INSERT IGNORE, a linha não será realmente inserida se resultar em uma chave duplicada. Mas a instrução não irá gerar um erro. Em vez disso, gera um aviso. Esses casos incluem:

  • Inserir uma chave duplicada em colunas com PRIMARY KEYou UNIQUErestrições.
  • Inserir um NULL em uma coluna com uma NOT NULLrestrição.
  • Inserir uma linha em uma tabela particionada, mas os valores inseridos não são mapeados para uma partição.

Se você usa REPLACE, o MySQL realmente faz um DELETEseguido por um INSERTinternamente, o que tem alguns efeitos colaterais inesperados:

  • Um novo ID de incremento automático é alocado.
  • Linhas dependentes com chaves estrangeiras podem ser excluídas (se você usar chaves estrangeiras em cascata) ou impedir a REPLACE.
  • Os gatilhos que disparam DELETEsão executados desnecessariamente.
  • Os efeitos colaterais também são propagados para réplicas.

correção: tanto REPLACEe INSERT...ON DUPLICATE KEY UPDATEestão fora do padrão, proprietária invenções específico para MySQL. ANSI SQL 2003 define uma MERGEdeclaração que pode resolver a mesma necessidade (e mais), mas o MySQL não suporta a MERGEdeclaração.


Um usuário tentou editar esta postagem (a edição foi rejeitada pelos moderadores). A edição tentou adicionar uma reivindicação que INSERT...ON DUPLICATE KEY UPDATEfaz com que um novo ID de incremento automático seja alocado. É verdade que o novo ID é gerado , mas não é usado na linha alterada.

Veja a demonstração abaixo, testada com o Percona Server 5.5.28. A variável de configuração innodb_autoinc_lock_mode=1(o padrão):

mysql> create table foo (id serial primary key, u int, unique key (u));
mysql> insert into foo (u) values (10);
mysql> select * from foo;
+----+------+
| id | u    |
+----+------+
|  1 |   10 |
+----+------+

mysql> show create table foo\G
CREATE TABLE `foo` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `u` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `u` (`u`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=latin1

mysql> insert into foo (u) values (10) on duplicate key update u = 20;
mysql> select * from foo;
+----+------+
| id | u    |
+----+------+
|  1 |   20 |
+----+------+

mysql> show create table foo\G
CREATE TABLE `foo` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `u` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `u` (`u`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=latin1

O exemplo acima demonstra que a instrução IODKU detecta a duplicata e chama a atualização para alterar o valor de u. Observe que AUTO_INCREMENT=3indica que um ID foi gerado, mas não usado na linha.

Considerando REPLACEque exclui a linha original e insere uma nova linha, gerando e armazenando um novo ID de incremento automático:

mysql> select * from foo;
+----+------+
| id | u    |
+----+------+
|  1 |   20 |
+----+------+
mysql> replace into foo (u) values (20);
mysql> select * from foo;
+----+------+
| id | u    |
+----+------+
|  3 |   20 |
+----+------+
Bill Karwin
fonte
3
Gostaria de saber se a equipe de desenvolvimento do mysql tem alguma intenção de adotar o MERGE do ANSI SQL 2003?
Lonnie Melhor
1
@LonnieBest: A solicitação de recurso para a implementação do MERGE foi feita em 2005, mas até onde eu sei, não há progresso ou plano. bugs.mysql.com/bug.php?id=9018
Bill Karwin
2
Ah, posso acrescentar que ele gera avisos (não erros) para incompatibilidade de tipo inválida, mas não gera um aviso para chave primária composta duplicada.
Fabrício Matté 22/08/2012
11
Acabei de olhar para uma tabela que foi preenchida por várias INSERT ... ON DUPLICATE KEY UPDATE ...declarações. Muitos dados são duplicados e resultaram em uma instância do AI PK aumentando de 17.029.941 para 46.271.740 entre duas linhas. Essa geração de uma nova IA sempre significa que seu alcance pode ser rapidamente preenchido e você precisa fazer a limpeza. Esta tabela tem apenas duas semanas!
Engineer81
4
@AntTheKnee, ahh, os desafios de trabalhar no tempo do Big Data.
Bill Karwin
174

Caso você queira ver o que tudo isso significa, aqui está um golpe por golpe:

CREATE TABLE `users_partners` (
  `uid` int(11) NOT NULL DEFAULT '0',
  `pid` int(11) NOT NULL DEFAULT '0',
  PRIMARY KEY (`uid`,`pid`),
  KEY `partner_user` (`pid`,`uid`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8

A chave primária é baseada nas duas colunas desta tabela de referência rápida. Uma chave primária requer valores exclusivos.

Vamos começar:

INSERT INTO users_partners (uid,pid) VALUES (1,1);
...1 row(s) affected

INSERT INTO users_partners (uid,pid) VALUES (1,1);
...Error Code : 1062
...Duplicate entry '1-1' for key 'PRIMARY'

INSERT IGNORE INTO users_partners (uid,pid) VALUES (1,1);
...0 row(s) affected

INSERT INTO users_partners (uid,pid) VALUES (1,1) ON DUPLICATE KEY UPDATE uid=uid
...0 row(s) affected

observe que o acima economizou muito trabalho extra ao definir a coluna igual a si mesma, nenhuma atualização realmente necessária

REPLACE INTO users_partners (uid,pid) VALUES (1,1)
...2 row(s) affected

e agora alguns testes de várias linhas:

INSERT INTO users_partners (uid,pid) VALUES (1,1),(1,2),(1,3),(1,4)
...Error Code : 1062
...Duplicate entry '1-1' for key 'PRIMARY'

INSERT IGNORE INTO users_partners (uid,pid) VALUES (1,1),(1,2),(1,3),(1,4)
...3 row(s) affected

nenhuma outra mensagem foi gerada no console e agora possui esses 4 valores nos dados da tabela. Eu apaguei tudo, exceto (1,1), para poder testar no mesmo campo de jogo

INSERT INTO users_partners (uid,pid) VALUES (1,1),(1,2),(1,3),(1,4) ON DUPLICATE KEY UPDATE uid=uid
...3 row(s) affected

REPLACE INTO users_partners (uid,pid) VALUES (1,1),(1,2),(1,3),(1,4)
...5 row(s) affected

Então aí está. Como tudo foi realizado em uma tabela nova, quase sem dados e sem produção, os tempos de execução foram microscópicos e irrelevantes. Qualquer pessoa com dados do mundo real seria mais que bem-vinda para contribuir com eles.

Paulus Maximus
fonte
Eu corri ambos na chave duplicada e substituí-lo. Minhas tabelas terminaram com ~ 120K linhas, com cerca de 30% das minhas linhas sendo duplicadas. Na chave duplicada, executada em 102 segundos e a substituição executada em 105 segundos. No meu caso, estou usando a chave duplicada.
Crunkchitis
1
Testou o acima com MariaDB 10 e recebeu um aviso ao executar INSERT IGNORE INTO users_partners (uid,pid) VALUES (1,1),(1,2),(1,3),(1,4).
Floris 24/05
Qual versão do MySQL você usou para tudo isso?
Radu Murzea 11/04/19
41

Algo importante a acrescentar: Ao usar o INSERT IGNORE e você tem violações de chave, o MySQL NÃO gera um aviso!

Se você tentar, por exemplo, inserir 100 registros por vez, com um defeituoso, entrará no modo interativo:

Query OK, 99 rows affected (0.04 sec)

Records: 100 Duplicates: 1 Warnings: 0

Como você vê: sem avisos! Esse comportamento é mesmo descrito incorretamente na documentação oficial do Mysql.

Se seu script precisar ser informado, se alguns registros não foram adicionados (devido a violações de teclas), você deve chamar mysql_info () e analisá-lo para o valor "Duplicados".

Jens
fonte
6
Se você estiver usando PHP, precisará mysqli_affected_rows()saber se o que INSERTrealmente aconteceu.
Amal Murali
Com o MySQL 5.5 e o MariaDB 10 , recebo um erro Cannot add or update a child row: a foreign key constraint fails e nenhuma linha (mesmo as válidas) são adicionadas.
Floris 24/05
2
@Floris Esse erro ocorre devido a uma restrição de chave estrangeira e não a uma chave duplicada . Estou usando o MySQL 5.5.28. Ao usar INSERT IGNORE, chaves duplicadas são ignoradas sem erro ou aviso.
toxalot
20

Eu uso rotineiramente INSERT IGNOREe parece exatamente o tipo de comportamento que você também está procurando. Desde que você saiba que as linhas que causariam conflitos de índice não serão inseridas e você planeje seu programa adequadamente, isso não deverá causar problemas.

David Z
fonte
4
Estou preocupado em ignorar outros erros além da duplicação. Isso está correto ou o comando INSERT IGNORE ignora apenas a falha de duplicação? Obrigado!
6117 Thomas G Henry
2
Transforma qualquer erro em um aviso. Veja uma lista desses casos na minha resposta.
22120 Bill Karwin
Isso é uma vergonha; Eu gostaria que isso ignorasse apenas as falhas duplicadas.
Lonnie Melhor
As principais violações causam erros ! Veja meu comentário na resposta de @Jens.
Floris
1
@Pacerier, depende se o seu aplicativo verifica se há avisos. Ou se pode verificar se há avisos. Por exemplo, a maioria dos pacotes ORM não oferece a oportunidade. Alguns conectores (por exemplo, JDBC) também o separam da API do MySQL, para que você não tenha a oportunidade de verificar avisos.
Bill Karwin
18

Sei que isso é antigo, mas adicionarei esta observação caso alguém (como eu) chegue a esta página enquanto tenta encontrar informações sobre INSERT..IGNORE.

Como mencionado acima, se você usar INSERT..IGNORE, os erros que ocorrem durante a execução da instrução INSERT serão tratados como avisos.

Uma coisa que não é mencionada explicitamente é que INSERT..IGNORE fará com que valores inválidos sejam ajustados aos valores mais próximos quando inseridos (enquanto valores inválidos causariam a interrupção da consulta se a palavra-chave IGNORE não fosse usada).

Chris
fonte
6
Não tenho muita certeza do que você quer dizer com "valores inválidos" e corrigiu o que? Você poderia fornecer um exemplo ou explicação adicional?
Marenz
4
Isso significa que, se você inserir o tipo de dados incorreto em um campo ao usar "INSERIR IGNORAR", os dados serão modificados para corresponder ao tipo de dados do campo e um valor potencialmente inválido será inserido, e a consulta continuará em execução. Somente com "INSERT", seria gerado um erro sobre o tipo de dados incorreto e a consulta seria abortada. Isso pode ser bom com um número sendo inserido em um varchar ou campo de texto, mas a inserção de uma sequência de texto em um campo com um tipo de dados numérico resultaria em dados incorretos.
Codewaggle
2
@Marenz outro exemplo: se sua tabela tiver uma coluna não nula e sua consulta "INSERT IGNORE" não especificar um valor para essa coluna, a linha será inserida com um valor zero nessa coluna, independentemente de o sql_mode estrito estar ativado .
Shannon
Bom ponto sobre valores inválidos! Este tópico é ótimo para aprender sobre "INSERIR IGNORAR", também deixarei meus 5 centavos: medium.com/legacy-systems-diary/… belo artigo com exemplos de quão cuidadoso você deve ser ao usar o "INSERIR IGNORAR" declaração.
0x49D1 11/11/19
8

ON DUPLICATE KEY UPDATE não está realmente no padrão. É tão padrão quanto REPLACE. Consulte SQL MERGE .

Essencialmente, ambos os comandos são versões de sintaxe alternativa dos comandos padrão.

Chris KL
fonte
1
substituir faz uma exclusão e inserção, enquanto a atualização de chave duplicada atualiza a linha existente. Algumas diferenças são: auto incremento ID, posição da linha, um monte de gatilhos
ahnbizcad
8

ReplaceInto parece ser uma opção. Ou você pode verificar com

IF NOT EXISTS(QUERY) Then INSERT

Isto irá inserir ou excluir e depois inserir. Costumo fazer um IF NOT EXISTScheque primeiro.

IEnumerator
fonte
Obrigado pela resposta rápida. Estou assumindo em todo o lugar, mas presumo que isso seria semelhante ao ON DUPLICATE KEY UPDATE, pois ele executaria atualizações desnecessárias. Parece um desperdício, mas não tenho certeza. Qualquer um desses deve funcionar. Gostaria de saber se alguém sabe o que é melhor.
446 Thomas G Henry
6
NTuplip - essa solução ainda está aberta para condições de corrida de inserções por transações simultâneas.
22410 Chris KL
REPLACEexclui todas as linhas na tabela combinando com qualquer PRIMARY ou UNIQUEchave, então INSERTs . Isso é potencialmente muito mais trabalho que o IODKU.
Rick James
4

Perigo potencial de INSERIR IGNORAR. Se você estiver tentando inserir um valor VARCHAR por mais tempo, a coluna foi definida com - o valor será truncado e inserido, mesmo que o modo estrito esteja ativado.

ri muito
fonte
3

Se usar insert ignoreuma SHOW WARNINGS;declaração no final do seu conjunto de consultas, será exibida uma tabela com todos os avisos, incluindo quais IDs foram as duplicatas.

Ray Foss
fonte
SHOW WARNINGS;apenas parece afetar a consulta mais recente. Quaisquer declarações anteriores não são acumuladas, se você tiver mais de uma única declaração.
Kawu
2

Se você deseja inserir na tabela e no conflito da chave primária ou do índice exclusivo, ele atualizará a linha conflitante em vez de inseri-la.

Sintaxe:

insert into table1 set column1 = a, column2 = b on duplicate update column2 = c;

Agora aqui, essa instrução de inserção pode parecer diferente do que você viu anteriormente. Esta instrução de inserção tentando inserir uma linha na tabela1 com o valor de aeb na coluna column1 e column2 respectivamente.

Vamos entender essa afirmação em profundidade:

Por exemplo: aqui coluna1 é definida como a chave primária na tabela1.

Agora, se na tabela1 não houver linha com o valor "a" na coluna1. Portanto, esta instrução irá inserir uma linha na tabela1.

Agora, se na tabela1 houver uma linha com o valor "a" na coluna2. Portanto, essa instrução atualizará o valor da coluna2 da linha com "c", onde o valor da coluna1 é "a".

Portanto, se você deseja inserir uma nova linha, atualize essa linha no conflito da chave primária ou do índice exclusivo.
Leia mais neste link

Dilraj Singh
fonte
0

INSERT...ON DUPLICATE KEY UPDATE é preferido para impedir o gerenciamento inesperado de exceções.

Esta solução funciona quando você possui ** 1 restrição exclusiva **

No meu caso, eu sei disso col1e col2faço um índice composto exclusivo.

Ele controla o erro, mas não lança uma exceção em duplicado. Em relação ao desempenho, a atualização com o mesmo valor é eficiente, pois o MySQL percebe isso e não o atualiza

INSERT INTO table
  (col1, col2, col3, col4)
VALUES
  (?, ?, ?, ?)
ON DUPLICATE KEY UPDATE
    col1 = VALUES(col1),
    col2 = VALUES(col2)

A idéia de usar essa abordagem veio dos comentários em phpdelusions.net/pdo .

micaball
fonte