"INSERT IGNORE" vs "INSERT ... ON DUPLICATE KEY UPDATE"

833

Beim Ausführen einer INSERTAnweisung mit vielen Zeilen möchte ich doppelte Einträge überspringen, die andernfalls zu Fehlern führen würden. Nach einigen Recherchen scheinen meine Optionen die Verwendung von entweder:

  • ON DUPLICATE KEY UPDATE was ein unnötiges Update um einige Kosten impliziert, oder
  • INSERT IGNORE Dies impliziert eine Einladung für andere Arten von Versäumnissen, unangekündigt einzusteigen.

Habe ich recht mit diesen Annahmen? Was ist der beste Weg, um einfach die Zeilen zu überspringen, die zu Duplikaten führen können, und einfach mit den anderen Zeilen fortzufahren?

Thomas G Henry
quelle

Antworten:

990

Ich würde empfehlen, zu verwenden INSERT...ON DUPLICATE KEY UPDATE.

Wenn Sie verwenden INSERT IGNORE, wird die Zeile nicht wirklich eingefügt, wenn dies zu einem doppelten Schlüssel führt. Die Anweisung generiert jedoch keinen Fehler. Stattdessen wird eine Warnung generiert. Diese Fälle umfassen:

  • Einfügen eines doppelten Schlüssels in Spalten mit PRIMARY KEYoder UNIQUEEinschränkungen.
  • Einfügen eines NULL in eine Spalte mit einer NOT NULLEinschränkung.
  • Einfügen einer Zeile in eine partitionierte Tabelle, aber die Werte, die Sie einfügen, werden keiner Partition zugeordnet.

Wenn Sie verwenden REPLACE, führt MySQL tatsächlich DELETEein INSERTinternes gefolgt von einem durch , was einige unerwartete Nebenwirkungen hat:

  • Eine neue Auto-Inkrement-ID wird zugewiesen.
  • Abhängige Zeilen mit Fremdschlüsseln können gelöscht werden (wenn Sie kaskadierende Fremdschlüssel verwenden) oder das verhindern REPLACE.
  • Auslöser, die ausgelöst werden, DELETEwerden unnötig ausgeführt.
  • Nebenwirkungen werden auch auf Replikate übertragen.

Korrektur: beides REPLACEund INSERT...ON DUPLICATE KEY UPDATEnicht standardmäßige, proprietäre Erfindungen, die für MySQL spezifisch sind. ANSI SQL 2003 definiert eine MERGEAnweisung, die denselben Bedarf (und mehr) lösen kann, aber MySQL unterstützt die MERGEAnweisung nicht.


Ein Benutzer hat versucht, diesen Beitrag zu bearbeiten (die Bearbeitung wurde von den Moderatoren abgelehnt). Bei der Bearbeitung wurde versucht, einen Anspruch hinzuzufügen, der INSERT...ON DUPLICATE KEY UPDATEbewirkt , dass eine neue ID für die automatische Inkrementierung zugewiesen wird. Es ist wahr, dass die neue ID generiert wird , aber sie wird nicht in der geänderten Zeile verwendet.

Siehe unten stehende Demonstration, getestet mit Percona Server 5.5.28. Die Konfigurationsvariable innodb_autoinc_lock_mode=1(Standardeinstellung):

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

Das Obige zeigt, dass die IODKU-Anweisung das Duplikat erkennt und die Aktualisierung aufruft, um den Wert von zu ändern u. Beachten Sie, AUTO_INCREMENT=3dass eine ID generiert, aber nicht in der Zeile verwendet wurde.

Während REPLACEdie ursprüngliche Zeile gelöscht und eine neue Zeile eingefügt wird, wird eine neue Auto-Inkrement-ID generiert und gespeichert:

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
quelle
3
Ich frage mich, ob das MySQL-Entwicklungsteam beabsichtigt, MERGE jemals aus ANSI SQL 2003 zu übernehmen.
Lonnie Best
1
@LonnieBest: Die Feature-Anfrage für die Implementierung von MERGE wurde 2005 gestellt, aber meines Wissens gibt es keine Fortschritte oder Pläne. bugs.mysql.com/bug.php?id=9018
Bill Karwin
2
Oh, ich kann hinzufügen, dass es Warnungen (keine Fehler) für ungültige Typinkongruenzen generiert, aber keine Warnung für duplizierten zusammengesetzten Primärschlüssel.
Fabrício Matté
11
Ich habe mir gerade eine Tabelle angesehen, die mit vielen INSERT ... ON DUPLICATE KEY UPDATE ...Aussagen gefüllt ist. Viele der Daten sind doppelt vorhanden, und es hat dazu geführt, dass eine Instanz der AI-PK zwischen zwei Zeilen von 17.029.941 auf 46.271.740 gestiegen ist. Diese Generation einer neuen KI bedeutet jedes Mal, dass Ihre Reichweite sehr schnell gefüllt werden kann und Sie aufräumen müssen. Dieser Tisch ist erst zwei Wochen alt!
Engineer81
4
@AntTheKnee, ahh, die Herausforderungen der Arbeit in der Zeit von Big Data.
Bill Karwin
174

Wenn Sie sehen möchten, was dies alles bedeutet, finden Sie hier einen Überblick über alles:

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

Der Primärschlüssel basiert auf beiden Spalten dieser Kurzreferenztabelle. Ein Primärschlüssel erfordert eindeutige Werte.

Lass uns anfangen:

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

Beachten Sie, dass durch das oben Gesagte zu viel zusätzliche Arbeit gespart wurde, indem die Spalte auf sich selbst gesetzt wurde. Es ist kein Update erforderlich

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

und jetzt einige mehrzeilige Tests:

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

In der Konsole wurden keine anderen Nachrichten generiert, und diese 4 Werte sind jetzt in den Tabellendaten enthalten. Ich habe alles außer (1,1) gelöscht, damit ich vom selben Spielfeld aus testen konnte

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

Da haben Sie es also. Da dies alles an einem frischen Tisch ohne Daten und ohne Produktion durchgeführt wurde, waren die Ausführungszeiten mikroskopisch und irrelevant. Jeder mit realen Daten wäre herzlich eingeladen, diese beizutragen.

Paulus Maximus
quelle
Ich habe beide auf doppelten Schlüssel ausgeführt und in ersetzt. Meine Tabellen endeten mit ~ 120.000 Zeilen, wobei etwa 30% meiner Zeilen Duplikate waren. Der doppelte Schlüssel lief in 102 Sekunden und das Ersetzen in 105 Sekunden. Für meinen Fall bleibe ich bei einem doppelten Schlüssel.
Crunkchitis
1
Habe das oben genannte mit MariaDB 10 getestet und beim Laufen eine Warnung erhalten INSERT IGNORE INTO users_partners (uid,pid) VALUES (1,1),(1,2),(1,3),(1,4).
Floris
Welche MySQL-Version haben Sie für all das verwendet?
Radu Murzea
41

Etwas Wichtiges hinzuzufügen: Wenn Sie INSERT IGNORE verwenden und Sie Schlüsselverletzungen haben, gibt MySQL KEINE Warnung aus!

Wenn Sie beispielsweise versuchen, 100 Datensätze gleichzeitig einzufügen, wobei einer fehlerhaft ist, gelangen Sie in den interaktiven Modus:

Query OK, 99 rows affected (0.04 sec)

Records: 100 Duplicates: 1 Warnings: 0

Wie Sie sehen: Keine Warnungen! Dieses Verhalten ist in der offiziellen MySQL-Dokumentation sogar falsch beschrieben.

Wenn Ihr Skript informiert werden muss und einige Datensätze nicht hinzugefügt wurden (aufgrund von Schlüsselverletzungen), müssen Sie mysql_info () aufrufen und es auf den Wert "Duplikate" analysieren.

Jens
quelle
6
Wenn Sie PHP verwenden, müssen Sie mysqli_affected_rows()wissen, ob das INSERTtatsächlich passiert ist.
Amal Murali
Sowohl mit MySQL 5.5 und 10 MariaDB ich tue einen Fehler Cannot add or update a child row: a foreign key constraint fails und keine Zeilen (auch gültig ist) hinzugefügt werden .
Floris
2
@Floris Dieser Fehler ist auf eine Fremdschlüsseleinschränkung und nicht auf einen doppelten Schlüssel zurückzuführen . Ich verwende MySQL 5.5.28. Bei der Verwendung INSERT IGNOREwerden doppelte Schlüssel ohne Fehler oder Warnung ignoriert.
Toxalot
20

Ich benutze INSERT IGNOREes routinemäßig und es klingt genau so, wie Sie es auch suchen. Solange Sie wissen, dass Zeilen, die Indexkonflikte verursachen würden, nicht eingefügt werden und Sie Ihr Programm entsprechend planen, sollte dies keine Probleme verursachen.

David Z.
quelle
4
Ich mache mir Sorgen, dass ich andere Fehler als das Duplizieren ignoriere. Ist dies korrekt oder ignoriert INSERT IGNORE nur den Duplizierungsfehler? Vielen Dank!
Thomas G Henry
2
Jeder Fehler wird zur Warnung. Eine Liste solcher Fälle finden Sie in meiner Antwort.
Bill Karwin
Das ist eine Schande; Ich wünschte, es würde nur die doppelten Fehler ignorieren.
Lonnie Best
Schlüsselverletzungen verursachen Fehler ! Siehe meinen Kommentar unter @Jens 'Antwort.
Floris
1
@ Pacerier, es hängt davon ab, ob Ihre Anwendung nach Warnungen sucht. Oder wenn es kann für Warnungen überprüfen. Beispielsweise bieten Ihnen die meisten ORM-Pakete keine Möglichkeit. Einige Connectors (z. B. JDBC) trennen Sie auch von der MySQL-API, sodass Sie keine Möglichkeit haben, Warnungen zu überprüfen.
Bill Karwin
18

Ich weiß, dass dies alt ist, aber ich werde diesen Hinweis hinzufügen, falls jemand anderes (wie ich) auf diese Seite gelangt, während er versucht, Informationen zu INSERT..IGNORE zu finden.

Wie oben erwähnt, werden bei Verwendung von INSERT..IGNORE Fehler, die beim Ausführen der INSERT-Anweisung auftreten, stattdessen als Warnungen behandelt.

Eine Sache, die nicht explizit erwähnt wird, ist, dass INSERT..IGNORE dazu führt, dass ungültige Werte beim Einfügen auf die nächsten Werte angepasst werden (während ungültige Werte dazu führen würden, dass die Abfrage abgebrochen wird, wenn das Schlüsselwort IGNORE nicht verwendet wird).

Chris
quelle
6
Ich bin mir nicht sicher, was Sie unter "ungültigen Werten" verstehen und auf was korrigiert? Könnten Sie ein Beispiel oder eine weitere Erklärung geben?
Marenz
4
Wenn Sie bei Verwendung von "INSERT IGNORE" den falschen Datentyp in ein Feld einfügen, werden die Daten an den Datentyp des Felds angepasst und ein möglicherweise ungültiger Wert eingefügt. Die Abfrage wird dann weiter ausgeführt. Nur mit "INSERT" würde ein Fehler bezüglich des falschen Datentyps ausgelöst und die Abfrage abgebrochen. Dies ist möglicherweise in Ordnung, wenn eine Zahl in ein Varchar- oder Textfeld eingefügt wird. Das Einfügen einer Textzeichenfolge in ein Feld mit einem numerischen Datentyp würde jedoch zu fehlerhaften Daten führen.
Codewaggle
2
@Marenz ein weiteres Beispiel: Wenn Ihre Tabelle eine Spalte ungleich Null enthält und Ihre Abfrage "INSERT IGNORE" keinen Wert für diese Spalte angibt, wird die Zeile mit einem Wert von Null in diese Spalte eingefügt, unabhängig davon, ob der strikte sql_mode aktiviert ist .
Shannon
Guter Punkt zu ungültigen Werten! Dieser Thread ist großartig, um etwas über "INSERT IGNORE" zu lernen. Ich lasse auch meine 5 Cent: übrig medium.com/legacy-systems-diary/… schöner Artikel mit den Beispielen, wie vorsichtig Sie bei der Verwendung von "INSERT IGNORE" sein sollten. Aussage.
0x49D1
8

ON DUPLICATE KEY UPDATE ist nicht wirklich im Standard. Es ist ungefähr so ​​Standard wie REPLACE. Siehe SQL MERGE .

Im Wesentlichen sind beide Befehle Versionen mit alternativer Syntax von Standardbefehlen.

Chris KL
quelle
1
Beim Ersetzen wird ein Löschen und Einfügen durchgeführt, während beim Aktualisieren des doppelten Schlüssels die vorhandene Zeile aktualisiert wird. Einige Unterschiede sind: automatische Inkrementierung der ID,
Zeilenposition
8

ReplaceInto scheint eine Option zu sein. Oder Sie können mit überprüfen

IF NOT EXISTS(QUERY) Then INSERT

Dies wird eingefügt oder gelöscht und dann eingefügt. Ich neige dazu, zuerst einen IF NOT EXISTSScheck zu machen.

IEnumerator
quelle
Danke für die schnelle Antwort. Ich gehe davon aus, dass dies überall der Fall ist, aber ich gehe davon aus, dass dies dem ON DUPLICATE KEY UPDATE insofern ähnlich ist, als es unnötige Aktualisierungen durchführen würde. Es scheint verschwenderisch, aber ich bin nicht sicher. All dies sollte funktionieren. Ich frage mich, ob jemand weiß, was am besten ist.
Thomas G Henry
6
NTuplip - Diese Lösung ist weiterhin offen für Race-Bedingungen von Einfügungen durch gleichzeitige Transaktionen.
Chris KL
REPLACEin der Tabelle löscht alle Zeilen mit passendem beliebigen PRIMARY oder UNIQUESchlüssel, dann INSERTs . Dies ist möglicherweise viel mehr Arbeit als IODKU.
Rick James
4

Mögliche Gefahr von INSERT IGNORE. Wenn Sie versuchen, einen VARCHAR-Wert länger als die Spalte einzufügen, wurde die Spalte mit definiert - der Wert wird abgeschnitten und eingefügt, AUCH WENN der strikte Modus aktiviert ist.

LOL
quelle
3

Wenn Sie insert ignoreeine SHOW WARNINGS;Anweisung am Ende Ihres Abfragesatzes verwenden, wird eine Tabelle mit allen Warnungen angezeigt, einschließlich der IDs, die die Duplikate waren.

Ray Foss
quelle
SHOW WARNINGS;scheint nur die letzte Abfrage zu beeinflussen. Alle vorherigen Anweisungen werden nicht akkumuliert, wenn Sie mehr als eine einzelne Anweisung haben.
Kawu
2

Wenn Sie in die Tabelle und in den Konflikt des Primärschlüssels oder des eindeutigen Index einfügen möchten, wird die widersprüchliche Zeile aktualisiert, anstatt diese Zeile einzufügen.

Syntax:

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

Hier sieht diese Einfügeanweisung möglicherweise anders aus als zuvor. Diese Einfügeanweisung versucht, eine Zeile in Tabelle1 mit dem Wert a und b in Spalte Spalte1 bzw. Spalte2 einzufügen.

Lassen Sie uns diese Aussage im Detail verstehen:

Beispiel: Hier ist Spalte1 als Primärschlüssel in Tabelle1 definiert.

Wenn es in Tabelle 1 keine Zeile mit dem Wert "a" in Spalte 1 gibt. Diese Anweisung fügt also eine Zeile in die Tabelle1 ein.

Wenn sich nun in Tabelle 1 eine Zeile mit dem Wert "a" in Spalte 2 befindet. Diese Anweisung aktualisiert also den Wert für Spalte2 der Zeile mit "c", wobei der Wert für Spalte1 "a" ist.

Wenn Sie also eine neue Zeile einfügen möchten, aktualisieren Sie diese Zeile andernfalls im Konflikt mit dem Primärschlüssel oder dem eindeutigen Index.
Lesen Sie mehr unter diesem Link

Dilraj Singh
quelle
0

INSERT...ON DUPLICATE KEY UPDATE wird bevorzugt, um die Verwaltung unerwarteter Ausnahmen zu verhindern.

Diese Lösung funktioniert nur, wenn Sie ** 1 eindeutige Einschränkung ** haben

In meinem Fall weiß ich das col1und col2erstelle einen eindeutigen zusammengesetzten Index.

Es verfolgt den Fehler, löst jedoch keine Ausnahme beim Duplizieren aus. In Bezug auf die Leistung ist das Update mit demselben Wert effizient, da MySQL dies bemerkt und nicht aktualisiert

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

Die Idee, diesen Ansatz zu verwenden, kam aus den Kommentaren unter phpdelusions.net/pdo .

Micaball
quelle