web-dev-qa-db-fra.com

Empêcher l'incrémentation automatique sur l'insertion de doublons MySQL

En utilisant MySQL 5.1.49, j'essaie d'implémenter un système de marquage Le problème que j'ai est avec une table avec deux colonnes: id(autoincrement), tag(unique varchar) (InnoDB)

Lors de l'utilisation de query, INSERT IGNORE INTO tablename SET tag="whatever", la valeur id d'incrémentation automatique augmente même si l'insertion a été ignorée.

Normalement, cela ne poserait pas de problème, mais je m'attends à de nombreuses tentatives d'insertion de doublons pour cette table particulière, ce qui signifie que ma prochaine valeur pour le champ id d'une nouvelle ligne sautera beaucoup trop.

Par exemple, je vais me retrouver avec une table avec disons 3 lignes mais mauvaise id

1   | test
8   | testtext
678 | testtextt

De plus, si je ne fais pas INSERT IGNORE et que je ne fais que des INSERT INTO normaux et que je gère l'erreur, le champ d'incrémentation automatique augmente toujours, de sorte que la prochaine insertion vraie est toujours une incrémentation automatique incorrecte.

Existe-t-il un moyen d’arrêter l’incrémentation automatique s’il existe une tentative de tentative de ligne en double INSERT?

Si je comprends bien pour MySQL 4.1, cette valeur n’augmentera pas, mais la dernière chose que je veux faire est de finir par faire beaucoup de déclarations SELECT à l’avance pour vérifier si les balises existent ou, pire encore, rétrograder ma version de MySQL.

37
robert

Vous pouvez modifier votre INSERT pour qu’il ressemble à ceci:

INSERT INTO tablename (tag)
SELECT $tag
FROM tablename
WHERE NOT EXISTS(
    SELECT tag
    FROM tablename
    WHERE tag = $tag
)
LIMIT 1

$tag est la balise (correctement citée ou bien fictive) que vous souhaitez ajouter si elle n’y figure pas déjà. Cette approche ne déclenchera même pas d'insertion (et le gaspillage subséquent par auto-incrémentation) si la balise est déjà présente. Vous pourriez probablement trouver SQL plus agréable que cela, mais ce qui précède devrait faire l'affaire.

Si votre table est correctement indexée, alors le SELECT supplémentaire pour le contrôle d'existence sera rapide et la base de données devra quand même effectuer ce contrôle.

Cette approche ne fonctionnera pas pour la première balise cependant. Vous pouvez associer à votre table de balises une balise qui, selon vous, finira toujours par être utilisée, ou vous pouvez effectuer une vérification séparée pour une table vide.

22
mu is too short

La documentation de MySQL pour v 5.5 indique: 

"If you use INSERT IGNORE and the row is ignored, the AUTO_INCREMENT counter 
is **not** incremented and LAST_INSERT_ID() returns 0, 
which reflects that no row was inserted."

Réf: http://dev.mysql.com/doc/refman/5.5/fr/information-functions.html#function_last-insert-id

Depuis la version 5.1, InnoDB dispose d'un verrouillage auto-incrémental configurable. Voir aussi http://dev.mysql.com/doc/refman/5.1/fr/innodb-auto-increment-handling.html#innodb-auto-inc ...

Solution: utilisez l'option innodb_autoinc_lock_mode = 0 (traditionnel).

14

Je viens de trouver ce petit bijou ...

http://www.timrosenblatt.com/blog/2008/03/21/insert-where-not-exists/

INSERT INTO [table name] SELECT '[value1]', '[value2]' FROM DUAL
WHERE NOT EXISTS(
    SELECT [column1] FROM [same table name]
    WHERE [column1]='[value1]'
    AND [column2]='[value2]' LIMIT 1
)

Si affectée = 1, elle est insérée; sinon, si affectéRows = 0, il y avait un doublon.

10
Jamie

J'ai trouvé que la réponse de mu est trop courte est utile, mais limite car elle ne permet pas d'insérer une table vide. J'ai trouvé une simple modification a fait l'affaire:

INSERT INTO tablename (tag)
SELECT $tag
FROM (select 1) as a     #this line is different from the other answer
WHERE NOT EXISTS(
    SELECT tag
    FROM tablename
    WHERE tag = $tag
)
LIMIT 1

Remplacer la table de la clause from par une "fausse" table (select 1) as a a permis à cette partie de renvoyer un enregistrement permettant l'insertion. J'utilise mysql 5.5.37. Merci mu pour m'avoir fait presque tout le chemin ....

10
Landon

Vous pouvez toujours ajouter ON DUPLICATE KEY UPDATELisez ici (pas exactement, mais cela résoud votre problème, semble-t-il).

D'après les commentaires, par @ravi

Que l'incrément se produise ou non dépend de paramètre innodb_autoinc_lock_mode. Si défini sur une valeur différente de zéro, le Le compteur d’augmentation automatique augmentera même si la touche ON DUPLICATE KEY est activée.

J'avais le même problème mais je ne voulais pas utiliser innodb_autoinc_lock_mode = 0 car j'avais l'impression de tuer une mouche avec un obusier.

Pour résoudre ce problème, j'ai fini par utiliser une table temporaire.

create temporary table mytable_temp like mytable;

Puis j'ai inséré les valeurs avec:

insert into mytable_temp values (null,'valA'),(null,'valB'),(null,'valC');

Après cela, vous faites simplement une autre insertion mais utilisez "not in" pour ignorer les doublons.

insert into mytable (myRow) select mytable_temp.myRow from mytable_temp 
where mytable_temp.myRow not in (select myRow from mytable);

Je n'ai pas testé cela pour la performance, mais il fait le travail et est facile à lire. Cela n’était important que si je travaillais avec des données constamment mises à jour, je ne pouvais donc pas ignorer les lacunes.

1
Thomas B

La réponse acceptée était utile, mais j’ai rencontré un problème lors de l’utilisation: si votre table ne contenait aucune entrée, elle ne fonctionnerait pas car la sélection utilisait la table donnée. le tableau est vide, il vous suffit également d'insérer le tableau à 2 endroits et l'insertion de variables à 1 endroit, moins pour vous tromper.

INSERT INTO database_name.table_name (a,b,c,d)
SELECT 
    i.*
FROM
    (SELECT 
        $a AS a, 
            $b AS b,
            $c AS c,
            $d AS d
            /*variables (properly escaped) to insert*/
    ) i
        LEFT JOIN        
    database_name.table_name o ON i.a = o.a AND i.b = o.b /*condition to not insert for*/
WHERE
    o.a IS NULL
LIMIT 1 /*Not needed as can only ever be one, just being sure*/

J'espère que vous le trouverez utile

1
Joseph Bailey