web-dev-qa-db-fra.com

MySQL INSERT IGNORE INTO & clés étrangères

Pourquoi dans MySQL, INSERT IGNORE INTO ne change pas les erreurs contrainte de clé étrangère en avertissements?

J'essaie d'insérer un certain nombre d'enregistrements dans une table et je m'attends à ce que MySQL supprime ceux qui entraînent une erreur, une erreur et insère le reste. Est-ce que quelqu'un a des suggestions?

Et le SET FOREIGN_KEY_CHECKS = 0; n'est pas ma réponse. Parce que je m'attends à ce que les lignes qui défient les contraintes ne soient pas du tout insérées.

Merci

35
Mehran

[NOUVELLE RÉPONSE]

Merci à @NeverEndingQueue d'avoir soulevé cette question. Il semble que MySQL a finalement résolu ce problème. Je ne sais pas dans quelle version ce problème a été corrigé pour la première fois, mais en ce moment j'ai testé avec la version suivante et le problème n'est plus là:

mysql> SHOW VARIABLES LIKE "%version%";
+-------------------------+------------------------------+
| Variable_name           | Value                        |
+-------------------------+------------------------------+
| innodb_version          | 5.7.22                       |
| protocol_version        | 10                           |
| slave_type_conversions  |                              |
| tls_version             | TLSv1,TLSv1.1                |
| version                 | 5.7.22                       |
| version_comment         | MySQL Community Server (GPL) |
| version_compile_machine | x86_64                       |
| version_compile_os      | Linux                        |
+-------------------------+------------------------------+

Pour être clair:

 mysql> INSÉRER IGNORE DANS L'ENFANT 
 -> VALEURS 
 -> (NULL, 1) 
 ->, (NULL, 2) 
 - >, (NULL, 3) 
 ->, (NULL, 4) 
 ->, (NULL, 5) 
 ->, (NULL, 6); 
 Requête OK, 4 lignes affectées, 2 avertissements (0,03 sec) 
 Enregistrements: 6 doublons: 2 avertissements: 2 

Pour mieux comprendre la signification de cette dernière requête et pourquoi elle montre que le problème est résolu, veuillez continuer avec l'ancienne réponse ci-dessous.

[ANCIENNE RÉPONSE]

Ma solution consiste à contourner le problème et la solution réelle sera toujours de résoudre le problème dans MySQL lui-même.

Les étapes suivantes ont résolu mon problème:

a. Envisagez d'avoir les tableaux et les données suivants:

mysql>
CREATE TABLE parent (id INT AUTO_INCREMENT NOT NULL
                     , PRIMARY KEY (id)
) ENGINE=INNODB;

mysql>
CREATE TABLE child (id INT AUTO_INCREMENT
                    , parent_id INT
                    , INDEX par_ind (parent_id)
                    , PRIMARY KEY (id)
                    , FOREIGN KEY (parent_id) REFERENCES parent(id)
                        ON DELETE CASCADE
                        ON UPDATE CASCADE
) ENGINE=INNODB;

mysql>
INSERT INTO parent
VALUES (NULL), (NULL), (NULL), (NULL), (NULL), (NULL);

mysql>
SELECT * FROM parent;
+----+
| id |
+----+
|  1 |
|  2 |
|  3 |
|  4 |
|  5 |
|  6 |
+----+

b. Maintenant, nous devons supprimer certaines des lignes pour illustrer le problème:

mysql>
DELETE FROM parent WHERE id IN (3, 5);

c. PROBLÈME: Le problème se pose lorsque vous essayez d'insérer les lignes enfants suivantes:

mysql>
INSERT IGNORE INTO child
VALUES
    (NULL, 1)
    , (NULL, 2)
    , (NULL, 3)
    , (NULL, 4)
    , (NULL, 5)
    , (NULL, 6);

ERROR 1452 (23000): Cannot add or update a child row: a foreign key constraint f
ails (`test`.`child`, CONSTRAINT `child_ibfk_1` FOREIGN KEY (`parent_id`) REFERE
NCES `parent` (`id`) ON DELETE CASCADE ON UPDATE CASCADE)

mysql>
SELECT * FROM child;
Empty set (0.00 sec)

Même si le mot clé IGNORE est utilisé, mais MySQL annule l'opération demandée car l'erreur générée n'est pas transformée en avertissement (comme elle le supposait). Maintenant que le problème est évident, voyons comment exécuter la dernière insertion dans l'instruction sans faire face à aucune erreur.

d. SOLUTION: Je vais encapsuler l'insertion dans l'instruction par quelques autres instructions constantes qui ne dépendent ni des enregistrements insérés, ni de leur nombre.

mysql>
SET FOREIGN_KEY_CHECKS = 0;

mysql>
INSERT INTO child
VALUES
    (NULL, 1)
    , (NULL, 2)
    , (NULL, 3)
    , (NULL, 4)
    , (NULL, 5)
    , (NULL, 6);

mysql>
DELETE FROM child WHERE parent_id NOT IN (SELECT id FROM parent);

mysql>
SET FOREIGN_KEY_CHECKS = 1;

Je sais que ce n'est pas optimal mais tant que MySQL n'a pas résolu le problème, c'est le meilleur que je connaisse. D'autant plus que toutes les instructions peuvent être exécutées en une seule requête si vous utilisez la bibliothèque mysqli en PHP.

34
Mehran

Je pense que la solution mysql la plus simple au problème consiste à se joindre à la table de clés étrangères pour vérifier les contraintes:

INSERT INTO child (parent_id, value2, value3)
SELECT p.id, @new_value2, @new_value3
FROM parent p WHERE p.id = @parent_id

Mais il semble étrange que vous souhaitiez supprimer des enregistrements en raison de clés étrangères manquantes. Par exemple, je préfère avoir une erreur INSERT IGNORE sur les vérifications de clé étrangère.

6
Doug Kress

Je crois INSERT IGNORE est destiné à ignorer les erreurs de la couche serveur, pas de la couche du moteur de stockage. Ainsi, cela aidera pour les erreurs de clé en double (c'est le cas d'utilisation principal) et certaines conversions de données, mais pas les erreurs de clé étrangère, qui proviennent de la couche du moteur de stockage.

Quant à votre condition spécifique:

J'essaie d'insérer un certain nombre d'enregistrements dans une table et je m'attends à ce que MySQL supprime ceux qui produisent une erreur, toute erreur et insère le reste. Est-ce que quelqu'un a des suggestions?

Pour cela, je recommande d'utiliser mysql -f pour le forcer à continuer de fonctionner malgré toute erreur. Ainsi, par exemple, si vous avez un fichier comme celui-ci:

insert into child (parent_id, ...) values (bad_parent_id, ...);
insert into child (parent_id, ...) values (good_parent_id, ...);

Ensuite, vous pouvez charger ce fichier comme ceci, ce qui insérera les bonnes lignes et ignorera l'erreur des mauvaises lignes:

mysql -f < inserts.sql
3
Ike Walker

Ce problème semble être résolu dans MySQL 5.7, voir https://bugs.mysql.com/bug.php?id=7885 .

Désormais, l'erreur de contrainte de clé étrangère est transformée en avertissement:

Ce problème existe dans les versions 5.1,5.5,5.6 mais je vois quelques améliorations apportées dans 5.7 où l'erreur a été convertie en avertissement au lieu d'erreur après WL # 6614.

1
ks1322

INSERT IGNORE n'est qu'une solution de contournement pour les paresseux. Vous ne devez pas insérer des enregistrements en double en premier lieu. En outre, une clé primaire/unique n'est pas la même chose qu'une clé étrangère. Et gardez à l'esprit que IGNORE ignorera également les autres erreurs et avertissements (division par zéro, troncatures de données), ce qui n'est généralement pas une bonne chose.

Dans ce cas, et presque à chaque fois, il est plus logique d'utiliser REPLACE au lieu de INSERT IGNORE. Une autre option est LA MISE À JOUR DE LA CLÉ DUPLICATE.

Dans votre cas, si vous ne pouvez pas supprimer les parents manquants avant l'insertion, vous pouvez gérer la même chose avec une table temporaire comme celle-ci:

create temporary table tmpTable (col1, col2, ...);
insert into tmpTable values (row1), (row2), ...; -- same insert you already had
alter table tmpTable add key (foreignColumnName); -- only if # of rows is big
insert into table select null /* AI id */, col1, col2 ...
  from tmpTable join parentTable on foreignColumnName = parent.id;

Cordialement, Jose.

0
Jose Canciani