"INSERISCI IGNORA" vs "INSERISCI ... IN AGGIORNAMENTO CHIAVE DUPLICATO"


833

Mentre eseguo INSERTun'istruzione con molte righe, voglio saltare voci duplicate che altrimenti causerebbero un errore. Dopo alcune ricerche, le mie opzioni sembrano essere l'uso di:

  • ON DUPLICATE KEY UPDATE che implica un aggiornamento non necessario a un certo costo, oppure
  • INSERT IGNORE il che implica un invito per altri tipi di fallimento a scivolare senza preavviso.

Ho ragione su questi presupposti? Qual è il modo migliore per saltare semplicemente le righe che potrebbero causare duplicati e continuare semplicemente con le altre righe?

Risposte:


991

Consiglierei di usare INSERT...ON DUPLICATE KEY UPDATE.

Se si utilizza INSERT IGNORE, la riga non verrà effettivamente inserita se risulta in una chiave duplicata. Ma la dichiarazione non genererà un errore. Genera invece un avviso. Questi casi includono:

  • Inserimento di una chiave duplicata in colonne con PRIMARY KEYo UNIQUEvincoli.
  • Inserimento di un NULL in una colonna con un NOT NULLvincolo.
  • Inserimento di una riga in una tabella partizionata, ma i valori inseriti non vengono mappati su una partizione.

Se lo usi REPLACE, MySQL esegue effettivamente un DELETEseguito INSERTinternamente, che ha alcuni effetti collaterali imprevisti:

  • Viene assegnato un nuovo ID di incremento automatico.
  • Le righe dipendenti con chiavi esterne possono essere eliminate (se si utilizzano chiavi esterne a cascata) oppure impedire il REPLACE.
  • I trigger che si accendono DELETEvengono eseguiti inutilmente.
  • Gli effetti collaterali si propagano anche alle repliche.

correzione: entrambe REPLACEe INSERT...ON DUPLICATE KEY UPDATEsono invenzioni proprietarie non standard specifiche di MySQL. ANSI SQL 2003 definisce MERGEun'istruzione che può risolvere lo stesso bisogno (e altro), ma MySQL non supporta l' MERGEistruzione.


Un utente ha provato a modificare questo post (la modifica è stata rifiutata dai moderatori). La modifica ha tentato di aggiungere un reclamo che INSERT...ON DUPLICATE KEY UPDATEdetermina l'allocazione di un nuovo ID di autoincremento. È vero che il nuovo ID viene generato , ma non viene utilizzato nella riga modificata.

Vedi la dimostrazione di seguito, testata con Percona Server 5.5.28. La variabile di configurazione innodb_autoinc_lock_mode=1(impostazione predefinita):

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

Quanto sopra dimostra che l'istruzione IODKU rileva il duplicato e richiama l'aggiornamento per modificare il valore di u. Nota che AUTO_INCREMENT=3indica che è stato generato un ID, ma non utilizzato nella riga.

Considerando REPLACEche elimina la riga originale e inserisce una nuova riga, generando e memorizzando un nuovo ID di incremento automatico:

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

3
Mi chiedo se il team di sviluppo mysql abbia intenzione di adottare MERGE da ANSI SQL 2003?
Lonnie Best

1
@LonnieBest: la richiesta di funzionalità per l'implementazione di MERGE è stata fatta nel 2005, ma per quanto ne so non ci sono progressi o piani. bugs.mysql.com/bug.php?id=9018
Bill Karwin,

2
Oh, posso aggiungere che genera avvisi (non errori) per mancata corrispondenza del tipo non valido ma non genera un avviso per la chiave primaria composita duplicata.
Fabrício Matté,

11
Ho appena visto un tavolo che è stato popolato da molte INSERT ... ON DUPLICATE KEY UPDATE ...dichiarazioni. Molti dati sono duplicati e ha comportato un aumento dell'istanza AI PK da 17.029.941 a 46.271.740 tra due file. Quella generazione di una nuova IA ogni volta significa che la tua gamma può essere riempita molto rapidamente e devi ripulirla. Questa tabella ha solo due settimane!
Ingegnere81,

4
@AntTheKnee, ahh, le sfide del lavoro ai tempi dei Big Data.
Bill Karwin,

174

Se vuoi vedere cosa significa tutto questo, ecco un colpo alla volta di tutto:

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 chiave primaria si basa su entrambe le colonne di questa tabella di riferimento rapido. Una chiave primaria richiede valori univoci.

Cominciamo:

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

notare che quanto sopra ha risparmiato troppo lavoro extra impostando la colonna uguale a se stessa, nessun aggiornamento effettivamente necessario

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

e ora alcuni test su più righe:

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

nessun altro messaggio è stato generato nella console e ora ha quei 4 valori nei dati della tabella. Ho eliminato tutto tranne (1,1) in modo da poter provare dallo stesso campo di gioco

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

Così il gioco è fatto. Dal momento che tutto ciò è stato eseguito su un nuovo tavolo quasi privo di dati e non in produzione, i tempi di esecuzione erano microscopici e irrilevanti. Chiunque disponga di dati del mondo reale sarebbe più che benvenuto a contribuire.


Ho eseguito entrambi su chiave duplicata e sostituire in. Le mie tabelle sono terminate con ~ 120K righe con circa il 30% delle mie righe duplicate. La chiave duplicata è stata eseguita in 102 secondi e la sostituzione è stata eseguita in 105 secondi. Per il mio caso, mi attengo alla chiave duplicata.
Crunkchitis,

1
Ho testato quanto sopra con MariaDB 10 e ho ricevuto un avviso durante l'esecuzione INSERT IGNORE INTO users_partners (uid,pid) VALUES (1,1),(1,2),(1,3),(1,4).
Floris,

Quale versione di MySQL hai usato per tutto questo?
Radu Murzea,

41

Qualcosa di importante da aggiungere: quando si utilizza INSERT IGNORE e si verificano violazioni chiave, MySQL NON genera un avviso!

Se, ad esempio, provi a inserire 100 record alla volta, con uno difettoso, otterrai la modalità interattiva:

Query OK, 99 rows affected (0.04 sec)

Records: 100 Duplicates: 1 Warnings: 0

Come vedi: nessun avvertimento! Questo comportamento è persino erroneamente descritto nella documentazione ufficiale di Mysql.

Se il tuo script deve essere informato, se alcuni record non sono stati aggiunti (a causa di violazioni chiave) devi chiamare mysql_info () e analizzarlo per il valore "Duplicati".


6
Se stai usando PHP, dovrai mysqli_affected_rows()sapere se INSERTè successo davvero.
Amal Murali,

Con entrambi MySQL 5.5 e MariaDB 10 mi faccio un errore Cannot add or update a child row: a foreign key constraint fails e nessuna riga sono aggiunti (anche quelli validi).
Floris

2
@Floris Tale errore è dovuto a un vincolo di chiave esterna e non a una chiave duplicata . Sto usando MySQL 5.5.28. Durante l'utilizzo INSERT IGNORE, le chiavi duplicate vengono ignorate senza errori o avvisi.
toxalot

20

Uso abitualmente INSERT IGNOREe suona esattamente come il tipo di comportamento che stai cercando. Finché sai che le righe che causerebbero conflitti di indice non verranno inserite e pianifichi il tuo programma di conseguenza, non dovrebbero causare problemi.


4
Sono preoccupato che ignorerò errori diversi dalla duplicazione. È corretto o INSERT IGNORE ignora ignora solo l'errore di duplicazione? Grazie!
Thomas G Henry,

2
Trasforma qualsiasi errore in un avvertimento. Vedi un elenco di questi casi nella mia risposta.
Bill Karwin,

È un peccato; Vorrei che ignorasse solo i doppi fallimenti.
Lonnie Best

Le violazioni chiave causano errori ! Vedi il mio commento alla risposta di @Jens.
Floris,

1
@Pacerier, dipende se l'applicazione verifica gli avvisi. O se è in grado di verificare la presenza di avvisi. Ad esempio, la maggior parte dei pacchetti ORM non ti danno l'opportunità. Alcuni connettori (ad esempio JDBC) ti separano anche dall'API MySQL in modo da non avere l'opportunità di controllare gli avvisi.
Bill Karwin,

18

So che è vecchio, ma aggiungerò questa nota nel caso in cui altri (come me) arrivino a questa pagina mentre provano a trovare informazioni su INSERT..IGNORE.

Come accennato in precedenza, se si utilizza INSERT..IGNORE, gli errori che si verificano durante l'esecuzione dell'istruzione INSERT vengono invece considerati avvisi.

Una cosa che non è esplicitamente menzionata è che INSERT..IGNORE causerà valori non validi verranno adattati ai valori più vicini quando inseriti (mentre valori non validi provocherebbero l'interruzione della query se la parola chiave IGNORE non fosse utilizzata).


6
Non sono davvero sicuro di cosa intendi con "valori non validi" e corretto a cosa? Potresti fornire un esempio o un'ulteriore spiegazione?
Marenz,

4
Ciò significa che se si inserisce un tipo di dati errato in un campo quando si utilizza "INSERT IGNORE", i dati verranno modificati in modo che corrispondano al tipo di dati del campo e verrà inserito un valore potenzialmente non valido, quindi la query continuerà a essere eseguita. Solo con "INSERT", verrebbe generato un errore sul tipo di dati errato e la query verrebbe interrotta. Questo potrebbe essere OK se un numero viene inserito in un varchar o in un campo di testo, ma l'inserimento di una stringa di testo in un campo con un tipo di dati numerico comporterebbe dati errati.
codewaggle

2
@Marenz un altro esempio: se la tua tabella ha una colonna non nulla e la tua query "INSERT IGNORE" non specifica un valore per quella colonna, la riga verrà inserita con un valore zero in quella colonna indipendentemente dal fatto che sql_mode sia abilitato .
Shannon,

Un buon punto sui valori non validi! Questo thread è ottimo per conoscere "INSERT IGNORE", lascerò anche i miei 5 centesimi: medium.com/legacy-systems-diary/… bell'articolo con gli esempi su come dovresti stare attento mentre usi "INSERT IGNORE" dichiarazione.
0x49D1

8

ON DUPLICATE KEY UPDATE non è proprio nello standard. È standard come REPLACE. Vedi SQL MERGE .

Essenzialmente entrambi i comandi sono versioni di sintassi alternative dei comandi standard.


1
sostituisce elimina e inserisce, mentre l'aggiornamento della chiave onduplicata aggiorna la riga esistente. alcune differenze sono: ID autoincremento, posizione riga, un sacco di trigger
ahnbizcad

8

ReplaceSembra un'opzione. Oppure puoi verificare con

IF NOT EXISTS(QUERY) Then INSERT

Questo inserirà o eliminerà quindi inserirà. Tendo a fare un IF NOT EXISTScontrollo prima.


Grazie per la risposta rapida. Sto assumendo dappertutto, ma presumo che questo sarebbe simile a ON DUPLICATE KEY UPDATE in quanto eseguirà un aggiornamento non necessario. Sembra uno spreco, ma non ne sono sicuro. Ognuno di questi dovrebbe funzionare. Mi chiedo se qualcuno sa qual è il migliore.
Thomas G Henry,

6
NTuplip: quella soluzione è ancora aperta alle condizioni di gara dagli inserimenti per transazioni simultanee.
Chris KL,

REPLACEelimina quindi tutte le righe della tabella con la corrispondenza di qualsiasi PRIMARY o UNIQUEchiave, quindi INSERTs . Questo è potenzialmente molto più lavoro di IODKU.
Rick James,

4

Potenziale pericolo di INSERT IGNORE. Se stai cercando di inserire un valore VARCHAR più a lungo della colonna è stata definita con - il valore verrà troncato e inserito ANCHE SE la modalità rigorosa è abilitata.


3

Se l'utilizzo di insert ignoreuna SHOW WARNINGS;dichiarazione alla fine del set di query mostrerà una tabella con tutti gli avvisi, inclusi gli ID che erano i duplicati.


SHOW WARNINGS;sembra influire solo sull'ultima query. Le dichiarazioni precedenti non vengono accumulate se si dispone di più di una singola istruzione.
Kawu,

2

Se si desidera inserire nella tabella e nel conflitto della chiave primaria o dell'indice univoco, aggiornerà la riga in conflitto invece di inserire quella riga.

Sintassi:

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

Ora qui, questa dichiarazione di inserimento potrebbe avere un aspetto diverso da quello che hai visto in precedenza. Questa istruzione insert tenta di inserire una riga nella tabella1 con il valore di aeb rispettivamente nella colonna1 e nella colonna2.

Comprendiamo in profondità questa affermazione:

Ad esempio: qui la colonna1 è definita come chiave primaria nella tabella1.

Ora se nella tabella1 non c'è riga con il valore "a" nella colonna1. Quindi questa affermazione inserirà una riga nella tabella1.

Ora se nella tabella1 c'è una riga con il valore "a" nella colonna2. Quindi questa affermazione aggiornerà il valore column2 della riga con "c" dove il valore column1 è "a".

Quindi, se si desidera inserire una nuova riga, aggiornare quella riga in caso di conflitto tra la chiave primaria o l'indice univoco.
Maggiori informazioni su questo link


0

INSERT...ON DUPLICATE KEY UPDATE è preferito per impedire la gestione imprevista delle eccezioni.

Questa soluzione funziona solo con ** 1 vincolo univoco **

Nel mio caso lo so col1ecol2 creo un indice composito unico.

Tiene traccia dell'errore, ma non genera un'eccezione per i duplicati. Per quanto riguarda le prestazioni, l'aggiornamento con lo stesso valore è efficiente poiché MySQL lo nota e non lo aggiorna

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

L'idea di utilizzare questo approccio è nata dai commenti su phpdelusions.net/pdo .

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.