«INSERT IGNORE» vs «INSERT… ON DUPLICATE KEY UPDATE»

833

Lors de l'exécution d'une INSERTinstruction avec plusieurs lignes, je souhaite ignorer les entrées en double qui, sinon, provoqueraient un échec. Après quelques recherches, mes options semblent être l'utilisation de:

  • ON DUPLICATE KEY UPDATE ce qui implique une mise à jour inutile à un certain coût, ou
  • INSERT IGNORE ce qui implique une invitation à d'autres types de non-glissement inopiné.

Ai-je raison dans ces hypothèses? Quelle est la meilleure façon d'ignorer simplement les lignes susceptibles de provoquer des doublons et de continuer sur les autres lignes?

Thomas G Henry
la source

Réponses:

991

Je recommanderais d'utiliser INSERT...ON DUPLICATE KEY UPDATE.

Si vous utilisez INSERT IGNORE, la ligne ne sera pas réellement insérée si elle aboutit à une clé en double. Mais l'instruction ne générera pas d'erreur. Il génère à la place un avertissement. Ces cas comprennent:

  • Insertion d'une clé en double dans les colonnes avec PRIMARY KEYou UNIQUEcontraintes.
  • Insertion d'un NULL dans une colonne avec une NOT NULLcontrainte.
  • Insertion d'une ligne dans une table partitionnée, mais les valeurs que vous insérez ne sont pas mappées sur une partition.

Si vous utilisez REPLACE, MySQL fait en fait un DELETEsuivi d'un INSERTinterne, ce qui a des effets secondaires inattendus:

  • Un nouvel ID d'incrémentation automatique est alloué.
  • Les lignes dépendantes avec des clés étrangères peuvent être supprimées (si vous utilisez des clés étrangères en cascade) ou bien empêcher le REPLACE.
  • Les déclencheurs qui se déclenchent DELETEsont exécutés inutilement.
  • Les effets secondaires se propagent également aux répliques.

correction: les deux REPLACEet INSERT...ON DUPLICATE KEY UPDATEsont des inventions propriétaires non standard, spécifiques à MySQL. ANSI SQL 2003 définit une MERGEinstruction qui peut résoudre le même besoin (et plus), mais MySQL ne prend pas en charge l' MERGEinstruction.


Un utilisateur a tenté de modifier cette publication (la modification a été rejetée par les modérateurs). La modification a tenté d'ajouter une revendication qui INSERT...ON DUPLICATE KEY UPDATEprovoque l'attribution d'un nouvel identifiant d'incrémentation automatique. Il est vrai que le nouvel identifiant est généré , mais il n'est pas utilisé dans la ligne modifiée.

Voir la démonstration ci-dessous, testée avec Percona Server 5.5.28. La variable de configuration innodb_autoinc_lock_mode=1(par défaut):

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

Ce qui précède montre que l'instruction IODKU détecte le doublon et appelle la mise à jour pour modifier la valeur de u. Notez que AUTO_INCREMENT=3indique qu'un identifiant a été généré, mais n'est pas utilisé dans la ligne.

Alors REPLACEque supprime la ligne d'origine et insère une nouvelle ligne, la génération et le stockage d'un nouvel identifiant d'incrémentation automatique:

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
la source
3
Je me demande si l'équipe de développement mysql a l'intention de jamais adopter MERGE depuis ANSI SQL 2003?
Lonnie Best
1
@LonnieBest: La demande de fonctionnalité pour implémenter MERGE a été faite en 2005, mais il n'y a aucun progrès ou plan à ma connaissance. bugs.mysql.com/bug.php?id=9018
Bill Karwin
2
Oh, je peux ajouter qu'il génère des avertissements (pas des erreurs) pour une non-correspondance de type non valide, mais il ne génère pas d'avertissement pour la clé primaire composite dupliquée.
Fabrício Matté
11
Je viens de regarder un tableau qui a été rempli par beaucoup de INSERT ... ON DUPLICATE KEY UPDATE ...déclarations. Beaucoup de données sont en double, et il en est résulté une instance de l'IA PK passant de 17 029 941 à 46 271 740 entre deux lignes. Cette génération d'une nouvelle IA à chaque fois signifie que votre gamme peut être très rapidement remplie et que vous devez nettoyer. Cette table n'a que deux semaines!
Engineer81
4
@AntTheKnee, ahh, les défis du travail à l'heure du Big Data.
Bill Karwin
174

Au cas où vous voudriez voir ce que tout cela signifie, voici un coup par coup de tout:

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

La clé primaire est basée sur les deux colonnes de ce tableau de référence rapide. Une clé primaire nécessite des valeurs uniques.

Commençons:

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

notez que ce qui précède a sauvé trop de travail supplémentaire en définissant la colonne égale à elle-même, aucune mise à jour réellement nécessaire

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

et maintenant quelques tests sur plusieurs lignes:

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

aucun autre message n'a été généré dans la console, et elle a maintenant ces 4 valeurs dans les données de la table. J'ai tout supprimé sauf (1,1) pour pouvoir tester à partir du même terrain de jeu

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

Alors voilà. Étant donné que tout cela a été effectué sur une nouvelle table avec presque aucune donnée et pas en production, les délais d'exécution étaient microscopiques et non pertinents. Toute personne disposant de données réelles serait plus que bienvenue à les fournir.

Paulus Maximus
la source
J'ai exécuté les deux sur une clé en double et je l'ai remplacé. Mes tables se sont terminées avec environ 120 000 lignes, environ 30% de mes lignes étant des doublons. La clé en double a fonctionné en 102 secondes et le remplacement a fonctionné en 105 secondes. Pour mon cas, je m'en tiens à la clé en double.
crunkchitis
1
J'ai testé ce qui précède avec MariaDB 10 et j'ai reçu un avertissement lors de l'exécution INSERT IGNORE INTO users_partners (uid,pid) VALUES (1,1),(1,2),(1,3),(1,4).
Floris
Quelle version de MySQL avez-vous utilisée pour tout cela?
Radu Murzea
41

Quelque chose d'important à ajouter: lorsque vous utilisez INSERT IGNORE et que vous avez des violations de clés, MySQL ne déclenche PAS d'avertissement!

Si vous essayez par exemple d'insérer 100 enregistrements à la fois, dont un défectueux, vous obtiendrez en mode interactif:

Query OK, 99 rows affected (0.04 sec)

Records: 100 Duplicates: 1 Warnings: 0

Comme vous le voyez: aucun avertissement! Ce comportement est même décrit à tort dans la documentation officielle de Mysql.

Si votre script doit être informé, si certains enregistrements n'ont pas été ajoutés (en raison de violations de clés), vous devez appeler mysql_info () et l'analyser pour la valeur "Duplicates".

Jens
la source
6
Si vous utilisez PHP, vous aurez besoin mysqli_affected_rows()de savoir si INSERTcela s'est réellement produit.
Amal Murali
Avec MySQL 5.5 et MariaDB 10 , j'obtiens une erreur Cannot add or update a child row: a foreign key constraint fails et aucune ligne (même valide) n'est ajoutée.
Floris
2
@Floris Cette erreur est due à une contrainte de clé étrangère et non à une clé en double . J'utilise MySQL 5.5.28. Lors de l'utilisation INSERT IGNORE, les clés en double sont ignorées sans erreur ni avertissement.
toxalot
20

J'utilise régulièrement INSERT IGNORE, et cela ressemble exactement au type de comportement que vous recherchez également. Tant que vous savez que les lignes qui provoqueraient des conflits d'index ne seront pas insérées et que vous planifiez votre programme en conséquence, cela ne devrait causer aucun problème.

David Z
la source
4
Je crains d'ignorer les erreurs autres que la duplication. Est-ce correct ou INSERT IGNORE ignore-t-il uniquement ignore-t-il l'échec de la duplication? Merci!
Thomas G Henry
2
Il transforme toute erreur en avertissement. Voir une liste de ces cas dans ma réponse.
Bill Karwin
C'est une honte; Je souhaite qu'il ne ferait qu'ignorer les échecs en double.
Lonnie Best
Les violations clés provoquent des erreurs ! Voir mon commentaire à la réponse de @Jens.
Floris
1
@Pacerier, cela dépend si votre application vérifie les avertissements. Ou s'il peut vérifier les avertissements. Par exemple, la plupart des packages ORM ne vous en donnent pas l'occasion. Certains connecteurs (par exemple JDBC) vous séparent également de l'API MySQL afin que vous n'ayez pas la possibilité de vérifier les avertissements.
Bill Karwin
18

Je sais que c'est ancien, mais j'ajouterai cette note au cas où quelqu'un d'autre (comme moi) arriverait sur cette page en essayant de trouver des informations sur INSERT..IGNORE.

Comme mentionné ci-dessus, si vous utilisez INSERT..IGNORE, les erreurs qui se produisent lors de l'exécution de l'instruction INSERT sont traitées comme des avertissements à la place.

Une chose qui n'est pas explicitement mentionnée est que INSERT..IGNORE entraînera des valeurs invalides seront ajustées aux valeurs les plus proches lors de l'insertion (alors que des valeurs invalides entraîneraient l'abandon de la requête si le mot clé IGNORE n'était pas utilisé).

Chris
la source
6
Je ne suis pas vraiment sûr de ce que vous entendez par "valeurs invalides" et corrigé de quoi? Pourriez-vous fournir un exemple ou une explication supplémentaire?
Marenz
4
Cela signifie que si vous insérez le mauvais type de données dans un champ lors de l'utilisation de "INSERT IGNORE", les données seront modifiées pour correspondre au type de données du champ et une valeur potentiellement invalide sera insérée, puis la requête continuera à s'exécuter. Avec "INSERT" uniquement, une erreur serait déclenchée à propos du type de données incorrect et la requête serait abandonnée. Cela peut être OK avec un nombre inséré dans un varchar ou un champ de texte, mais l'insertion d'une chaîne de texte dans un champ avec un type de données numérique entraînerait des données incorrectes.
codewaggle
2
@Marenz un autre exemple: si votre table a une colonne non nulle et que votre requête "INSERT IGNORE" ne spécifie pas de valeur pour cette colonne, la ligne sera insérée avec une valeur nulle dans cette colonne, que le mode sql_mode strict soit activé ou non .
Shannon
Bon point sur les valeurs invalides! Ce fil est idéal pour en apprendre davantage sur "INSÉRER IGNORE", je vais également laisser mes 5 cents: medium.com/legacy-systems-diary/… bel article avec des exemples sur la façon dont vous devez être prudent lorsque vous utilisez le "INSÉRER IGNORE" déclaration.
0x49D1
8

ON DUPLICATE KEY UPDATE n'est pas vraiment dans la norme. C'est à peu près aussi standard que REMPLACER. Voir SQL MERGE .

Les deux commandes sont essentiellement des versions à syntaxe alternative des commandes standard.

Chris KL
la source
1
replace effectue une suppression et une insertion, tandis que la mise à jour de clé en double met à jour la ligne existante. quelques différences sont: id d'incrémentation automatique, position de la ligne, un tas de déclencheurs
ahnbizcad
8

ReplaceEn semble être une option. Ou vous pouvez vérifier avec

IF NOT EXISTS(QUERY) Then INSERT

Cela va insérer ou supprimer puis insérer. J'ai tendance à faire un IF NOT EXISTSchèque en premier.

IEnumerator
la source
Merci pour la réponse rapide. Je suppose partout, mais je suppose que ce serait similaire à ON DUPLICATE KEY UPDATE en ce qu'il effectuerait une mise à jour inutile. Cela semble gaspiller, mais je ne suis pas sûr. N'importe lequel de ces éléments devrait fonctionner. Je me demande si quelqu'un sait ce qui est le mieux.
Thomas G Henry,
6
NTuplip - cette solution est toujours ouverte aux conditions de concurrence des insertions par des transactions simultanées.
Chris KL
REPLACEsupprime toutes les lignes de la table avec correspondance tout PRIMARY ou UNIQUEclé, puis INSERTs . C'est potentiellement beaucoup plus de travail que IODKU.
Rick James
4

Danger potentiel d'INSERER IGNORE. Si vous essayez d'insérer une valeur VARCHAR plus longtemps, la colonne a été définie avec - la valeur sera tronquée et insérée MÊME SI le mode strict est activé.

LOL
la source
3

Si vous utilisez insert ignoreune SHOW WARNINGS;instruction à la fin de votre ensemble de requêtes, un tableau affiche tous les avertissements, y compris les ID qui étaient les doublons.

Ray Foss
la source
SHOW WARNINGS;ne semble affecter que la dernière requête. Les relevés précédents ne sont pas cumulés, si vous en avez plusieurs.
Kawu
2

Si vous souhaitez insérer dans la table et sur le conflit de la clé primaire ou de l'index unique, il mettra à jour la ligne en conflit au lieu d'insérer cette ligne.

Syntaxe:

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

Maintenant, ici, cette instruction d'insertion peut différer de ce que vous avez vu précédemment. Cette instruction d'insertion tente d'insérer une ligne dans le tableau1 avec la valeur de a et b dans la colonne colonne1 et colonne2 respectivement.

Comprenons cette déclaration en profondeur:

Par exemple: ici column1 est défini comme la clé primaire dans table1.

Maintenant, si dans le tableau 1, aucune ligne n'a la valeur «a» dans la colonne 1. Cette instruction insérera donc une ligne dans la table1.

Maintenant, si dans le tableau 1, il y a une ligne ayant la valeur «a» dans la colonne 2. Ainsi, cette instruction mettra à jour la valeur column2 de la ligne avec «c» où la valeur column1 est «a».

Donc, si vous souhaitez insérer une nouvelle ligne, sinon mettez à jour cette ligne sur le conflit de la clé primaire ou de l'index unique.
En savoir plus sur ce lien

Dilraj Singh
la source
0

INSERT...ON DUPLICATE KEY UPDATE est préférable pour éviter la gestion des exceptions inattendues.

Cette solution fonctionne lorsque vous avez ** 1 contrainte unique ** uniquement

Dans mon cas, je le sais col1et je crée col2un index composite unique.

Il garde la trace de l'erreur, mais ne lance pas d'exception sur les doublons. En ce qui concerne les performances, la mise à jour de la même valeur est efficace car MySQL le remarque et ne la met pas à jour

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

L'idée d'utiliser cette approche est venue des commentaires sur phpdelusions.net/pdo .

micaball
la source