web-dev-qa-db-fra.com

"INSERT IGNORE" vs "INSERT ... SUR LA MISE À JOUR DE CLÉ EN DOUBLE"

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

  • ON DUPLICATE KEY UPDATE qui implique une mise à jour inutile à un coût quelconque, ou
  • INSERT IGNORE qui implique une invitation pour d'autres types d'échec à se glisser sans préavis.

Ai-je raison dans ces hypothèses? Quelle est la meilleure façon de simplement ignorer les lignes qui pourraient causer des doublons et de simplement passer aux autres lignes?

814
Thomas G Henry

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

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

  • Insertion d'une clé dupliquée dans les colonnes avec des contraintes PRIMARY KEY ou UNIQUE.
  • Insertion d'un NULL dans une colonne avec une contrainte NOT NULL.
  • Insertion d'une ligne dans une table partitionnée, mais les valeurs que vous insérez ne correspondent pas à une partition.

Si vous utilisez REPLACE, MySQL effectue en fait un DELETE suivi d'un INSERT en interne, ce qui a des effets secondaires inattendus:

  • Un nouvel ID d'incrémentation automatique est attribué.
  • 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 empêcher le REPLACE.
  • Les déclencheurs qui se déclenchent sur DELETE sont exécutés inutilement.
  • Les effets secondaires sont également propagés aux esclaves de réplication.

correction:REPLACE et INSERT...ON DUPLICATE KEY UPDATE sont des inventions propriétaires non standard spécifiques à MySQL. ANSI SQL 2003 définit une instruction MERGE qui peut résoudre le même besoin (et davantage), mais MySQL ne prend pas en charge l'instruction MERGE.


Un utilisateur a tenté de modifier ce message (la modification a été rejetée par les modérateurs). La modification a tenté d'ajouter une revendication selon laquelle INSERT...ON DUPLICATE KEY UPDATE provoque l'attribution d'un nouvel ID d'auto-incrémentation. 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 (valeur 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=3 indique qu'un identifiant a été généré mais non utilisé dans la ligne.

Alors que REPLACE supprime la ligne d'origine et insère une nouvelle ligne, générant et stockant un nouvel identifiant d'auto-incrémentation:

mysql> select * from foo;
+----+------+
| id | u    |
+----+------+
|  1 |   20 |
+----+------+
mysql> replace into foo (u) values (20);
mysql> select * from foo;
+----+------+
| id | u    |
+----+------+
|  3 |   20 |
+----+------+
965
Bill Karwin

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

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 cette table 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 ceci a épargné trop de travail supplémentaire en réglant la colonne sur elle-même, aucune mise à jour n'est 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 il contient maintenant ces 4 valeurs dans les données de la table. J'ai tout supprimé sauf (1,1) pour pouvoir tester depuis le même terrain

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

Donc là vous l'avez. Etant donné que tout cela a été effectué sur une table fraîche avec pratiquement aucune donnée et qu’elle n’est pas en production, les délais d’exécution étaient microscopiques et sans importance. Quiconque disposant de données du monde réel serait le bienvenu pour y contribuer.

168
Paulus Maximus

Quelque chose d’important à ajouter: lorsque vous utilisez INSERT IGNORE et que vous avez des violations clés, MySQL n’émet aucun avertissement!

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

Query OK, 99 rows affected (0.04 sec)

Records: 100 Duplicates: 1 Warnings: 0

Comme vous le voyez: pas d'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é), vous devez appeler mysql_info () et l'analyser pour obtenir la valeur "Duplicates".

39
Jens

J'utilise couramment INSERT IGNORE, et cela ressemble exactement au type de comportement que vous recherchez. Tant que vous saurez que les lignes susceptibles de provoquer des conflits d’index ne seront pas insérées et que vous planifiez votre programme en conséquence, cela ne devrait pas poser de problème.

18
David Z

Je sais que c'est vieux, mais je vais ajouter 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 indiqué ci-dessus, si vous utilisez INSERT..IGNORE, les erreurs qui se produisent lors de l'exécution de l'instruction INSERT sont traitées à la place comme des avertissements.

Une chose qui n’est pas explicitement mentionnée est que INSERT..IGNORE entraînera des valeurs non valides qui seront ajustées aux valeurs les plus proches lorsqu’elles seront insérées (alors que des valeurs non valides provoqueraient l’abandon de la requête si le mot clé IGNORE n’était pas utilisé).

17
Chris

Replace Into 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 à aller d'abord pour un IF NOT EXISTS.

8
IEnumerator

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

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

8
Chris KL

Danger potentiel d'INSERT IGNORE. Si vous essayez d'insérer une valeur VARCHAR plus longue que la colonne a été définie avec - la valeur sera tronquée et insérée même si le mode strict est activé.

3
LOL

Si vous utilisez insert ignore, le fait d'avoir une instruction SHOW WARNINGS; à la fin de votre ensemble de requêtes affichera une table avec tous les avertissements, y compris les ID en double.

2
Ray Foss

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

Syntaxe:

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

Maintenant, ici, cette déclaration peut sembler différente de ce que vous avez vu précédemment. Cette instruction insert essayant d'insérer une ligne dans table1 avec les valeurs de a et b dans les colonnes column1 et column2, respectivement.

Comprenons cette déclaration en profondeur:

Par exemple: ici, colonne1 est définie comme clé primaire dans table1.

Maintenant, si dans la table1, il n'y a pas de ligne ayant la valeur "a" dans la colonne1. Donc, cette déclaration va insérer une ligne dans la table1.

Maintenant, si dans la table1, il existe une ligne ayant la valeur "a" dans la colonne2. Ainsi, cette instruction mettra à jour la valeur column2 de la ligne avec "c", la valeur column1 étant "a".

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

2
Dilraj Singh