„INSERT IGNORE” vs „INSERT… ON DUPLICATE KEY UPDATE”

833

Podczas wykonywania INSERTinstrukcji z wieloma wierszami chcę pominąć zduplikowane wpisy, które w przeciwnym razie spowodowałyby błąd. Po niektórych badaniach wydaje się, że moimi opcjami są:

  • ON DUPLICATE KEY UPDATE co oznacza niepotrzebną aktualizację za pewną opłatą, lub
  • INSERT IGNORE co oznacza zaproszenie na inne rodzaje niezapowiedzianych niespodzianek.

Czy mam rację w tych założeniach? Jaki jest najlepszy sposób, aby po prostu pominąć wiersze, które mogą powodować duplikaty, i po prostu przejść do innych wierszy?

Thomas G Henry
źródło

Odpowiedzi:

990

Poleciłbym użyć INSERT...ON DUPLICATE KEY UPDATE.

Jeśli użyjesz INSERT IGNORE, wiersz nie zostanie wstawiony, jeśli spowoduje powstanie duplikatu klucza. Ale instrukcja nie wygeneruje błędu. Zamiast tego generuje ostrzeżenie. Przypadki te obejmują:

  • Wstawianie duplikatu klucza w kolumnach z PRIMARY KEYlub z UNIQUEograniczeniami.
  • Wstawianie wartości NULL do kolumny z NOT NULLograniczeniem.
  • Wstawianie wiersza do tabeli podzielonej na partycje, ale wstawiane wartości nie są mapowane na partycję.

Jeśli używasz REPLACEMySQL faktycznie robi DELETEpo którym następuje INSERTwewnętrznie, który ma pewne nieoczekiwane skutki uboczne:

  • Przydzielono nowy identyfikator automatycznego przyrostu.
  • Zależne wiersze z kluczami obcymi mogą zostać usunięte (jeśli używasz kaskadowych kluczy obcych) lub też uniemożliwić REPLACE.
  • Wyzwalacze, które DELETEsię uruchamiają, są wykonywane niepotrzebnie.
  • Efekty uboczne są również propagowane do replik.

korekta: zarówno REPLACEi INSERT...ON DUPLICATE KEY UPDATEsą niestandardowe, opatentowane wynalazki specyficzne dla MySQL. ANSI SQL 2003 definiuje MERGEinstrukcję, która może zaspokoić tę samą potrzebę (i więcej), ale MySQL nie obsługuje tej MERGEinstrukcji.


Użytkownik próbował edytować ten post (zmiana została odrzucona przez moderatorów). Edycja próbowała dodać oświadczenie, które INSERT...ON DUPLICATE KEY UPDATEpowoduje przydzielenie nowego identyfikatora automatycznego przyrostu. To prawda, że ​​nowy identyfikator jest generowany , ale nie jest używany w zmienionym wierszu.

Zobacz demonstrację poniżej, przetestowaną na Percona Server 5.5.28. Zmienna konfiguracyjna innodb_autoinc_lock_mode=1(domyślna):

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

Powyższe pokazuje, że instrukcja IODKU wykrywa duplikat i wywołuje aktualizację w celu zmiany wartości u. Uwaga AUTO_INCREMENT=3wskazuje, że identyfikator został wygenerowany, ale nie został użyty w wierszu.

Podczas REPLACEgdy usuwa oryginalny wiersz i wstawia nowy wiersz, generując i przechowując nowy identyfikator automatycznego przyrostu:

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
źródło
3
Zastanawiam się, czy zespół programistów mysql ma kiedykolwiek zamiar przyjąć MERGE z ANSI SQL 2003?
Lonnie Best
1
@LonnieBest: Wniosek o wprowadzenie funkcji MERGE został złożony w 2005 r., Ale o ile wiem, nie ma postępu ani planu. bugs.mysql.com/bug.php?id=9018
Bill Karwin
2
Och, mogę dodać, że generuje ostrzeżenia (a nie błędy) dla niedopasowania typu nieprawidłowego, ale nie generuje ostrzeżenia dla duplikatu złożonego klucza głównego.
Fabrício Matté
11
Właśnie patrzyłem na tabelę wypełnioną wieloma INSERT ... ON DUPLICATE KEY UPDATE ...stwierdzeniami. Wiele danych jest duplikowanych, co spowodowało wzrost jednej instancji AI PK z 17,029,941 do 46 271 740 między dwoma rzędami. Ta generacja nowej sztucznej inteligencji za każdym razem oznacza, że ​​twój zasięg może być bardzo szybko zapełniony i musisz posprzątać. Ten stół ma dopiero dwa tygodnie!
Engineer81
4
@AntTheKnee, ahh, wyzwania związane z pracą w czasach Big Data.
Bill Karwin
174

Jeśli chcesz zobaczyć, co to wszystko znaczy, oto cios wszystkiego po wszystkim:

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

Klucz podstawowy opiera się na obu kolumnach tej tabeli szybkich odniesień. Klucz podstawowy wymaga unikalnych wartości.

Zaczynajmy:

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

Uwaga: powyższe zaoszczędziło zbyt wiele dodatkowej pracy, ustawiając kolumnę na taką samą, nie wymagając żadnej aktualizacji

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

a teraz niektóre testy wielu wierszy:

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

żadne inne komunikaty nie zostały wygenerowane w konsoli, a teraz ma te 4 wartości w danych tabeli. Usunąłem wszystko oprócz (1,1), aby móc testować z tego samego pola gry

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

Więc masz to. Ponieważ wszystko to zostało wykonane na świeżym stole bez prawie żadnych danych i nie w produkcji, czasy wykonania były mikroskopijne i nieistotne. Każdy, kto ma rzeczywiste dane, chętnie je włączy.

Paulus Maximus
źródło
Uruchomiłem oba na duplikacie klucza i zamieniłem na. Moje tabele zakończyły się ~ 120 000 wierszy, a około 30% moich wierszy jest duplikatami. Na duplikat klucza uruchomiono w 102 sekund i zastąpił uruchomiony w 105 sekund. W moim przypadku trzymam się duplikatu klucza.
zapalenie trzustki
1
Testowałem powyższe z MariaDB 10 i dostałem ostrzeżenie podczas działania INSERT IGNORE INTO users_partners (uid,pid) VALUES (1,1),(1,2),(1,3),(1,4).
Floris
Jakiej wersji MySQL używałeś do tego wszystkiego?
Radu Murzea
41

Coś ważnego do dodania: Gdy używasz INSERT IGNORE i masz poważne naruszenia zasad, MySQL NIE wyświetla ostrzeżenia!

Jeśli na przykład spróbujesz wstawić 100 rekordów jednocześnie, z jednym wadliwym, uzyskasz tryb interaktywny:

Query OK, 99 rows affected (0.04 sec)

Records: 100 Duplicates: 1 Warnings: 0

Jak widać: brak ostrzeżeń! To zachowanie jest nawet błędnie opisane w oficjalnej dokumentacji MySQL.

Jeśli twój skrypt wymaga powiadomienia, jeśli niektóre rekordy nie zostały dodane (z powodu naruszenia klucza), musisz wywołać mysql_info () i przeanalizować go pod kątem wartości „Duplikaty”.

Jens
źródło
6
Jeśli używasz PHP, musisz użyć, mysqli_affected_rows()aby wiedzieć, czy INSERTfaktycznie się to wydarzyło.
Amal Murali
Zarówno z MySQL 5.5 i MariaDB 10 I zrobić pojawia się błąd Cannot add or update a child row: a foreign key constraint fails i nie wiersze (nawet ważne nich) są dodawane.
Floris
2
@Floris Ten błąd jest spowodowany ograniczeniem klucza obcego, a nie duplikatem klucza . Używam MySQL 5.5.28. Podczas używania INSERT IGNOREduplikaty kluczy są ignorowane bez błędu lub ostrzeżenia.
toxalot
20

Rutynowo używam INSERT IGNOREi brzmi to dokładnie takie zachowanie, jakiego szukasz. Tak długo, jak wiesz, że wiersze, które spowodowałyby konflikty indeksów, nie zostaną wstawione i odpowiednio planujesz swój program, nie powinno to powodować żadnych problemów.

David Z
źródło
4
Obawiam się, że zignoruję błędy inne niż powielanie. Czy to jest poprawne, czy INSERT IGNORE ignoruje tylko ignoruje błąd duplikacji? Dzięki!
Thomas G Henry,
2
Zmienia każdy błąd w ostrzeżenie. Zobacz listę takich przypadków w mojej odpowiedzi.
Bill Karwin
Jaka szkoda; Chciałbym, żeby to zignorowało tylko duplikaty awarii.
Lonnie Best
Kluczowe naruszenia powodują błędy ! Zobacz mój komentarz w odpowiedzi @Jens.
Floris
1
@Pacerier, zależy to od tego, czy aplikacja sprawdza ostrzeżenia. Lub jeśli może sprawdzić ostrzeżenia. Na przykład większość pakietów ORM nie daje takiej możliwości. Niektóre konektory (na przykład JDBC) również oddzielają cię od MySQL API, więc nie masz możliwości sprawdzenia ostrzeżeń.
Bill Karwin,
18

Wiem, że to stare, ale dodam tę notatkę na wypadek, gdyby ktoś (jak ja) przybył na tę stronę, próbując znaleźć informacje na temat INSERT..IGNORE.

Jak wspomniano powyżej, jeśli użyjesz INSERT..IGNORE, błędy występujące podczas wykonywania instrukcji INSERT są traktowane jako ostrzeżenia.

Jedną rzeczą, która nie jest wyraźnie wymieniona, jest to, że INSERT..IGNORE spowoduje, że niepoprawne wartości zostaną dostosowane do najbliższych wartości po wstawieniu (podczas gdy niepoprawne wartości spowodują przerwanie zapytania, jeśli nie zostanie użyte słowo kluczowe IGNORE).

Chris
źródło
6
Nie jestem do końca pewien, co rozumiesz przez „nieprawidłowe wartości” i co poprawiłeś? Czy możesz podać przykład lub dodatkowe wyjaśnienie?
Marenz
4
Oznacza to, że jeśli wstawisz niepoprawny typ danych w polu podczas korzystania z „WSTAW IGNORZĘ”, dane zostaną zmodyfikowane, aby pasowały do ​​typu danych pola, i zostanie wstawiona potencjalnie niepoprawna wartość, a następnie zapytanie będzie kontynuowane. Tylko w przypadku „WSTAW” pojawi się błąd dotyczący niepoprawnego typu danych, a zapytanie zostanie przerwane. Może to być poprawne, gdy liczba jest wstawiana do varchar lub pola tekstowego, ale wstawienie ciągu tekstowego do pola o numerycznym typie danych spowodowałoby złe dane.
kodewaggle 18.12.12
2
@Marenz inny przykład: jeśli tabela ma niepustą kolumnę, a zapytanie „INSERT IGNORE” nie określa wartości dla tej kolumny, wiersz zostanie wstawiony z zerową wartością w tej kolumnie, niezależnie od tego, czy włączony jest tryb ścisły sql_mode .
Shannon,
Dobra uwaga na temat nieprawidłowych wartości! Ten wątek jest świetny do nauki o „INSERT IGNORE”, zostawię też 5 centów: medium.com/legacy-systems-diary/... fajny artykuł z przykładami, jak ostrożnie powinieneś używać „INSERT IGNORE” komunikat.
0x49D1,
8

NA DUPLIKACIE KLUCZOWA AKTUALIZACJA nie jest tak naprawdę standardem. Jest to tak standardowe, jak REPLACE. Zobacz scalanie SQL .

Zasadniczo oba polecenia są wersjami standardowych poleceń o alternatywnej składni.

Chris KL
źródło
1
replace powoduje usunięcie i wstawienie, natomiast aktualizacja klucza onduplicate aktualizuje istniejący wiersz. niektóre różnice to: auto inkrementujący identyfikator, pozycja wiersza, kilka wyzwalaczy
ahnbizcad
8

ReplaceOpcja wydaje się być opcją. Lub możesz to sprawdzić za pomocą

IF NOT EXISTS(QUERY) Then INSERT

Spowoduje to wstawienie lub usunięcie, a następnie wstawienie. I mają tendencję, aby przejść do IF NOT EXISTSsprawdzenia w pierwszej kolejności.

IEnumerator
źródło
Dziękuję za szybką odpowiedź. Zakładam, że wszędzie, ale zakładam, że byłoby to podobne do ON DUPLICATE KEY UPDATE, ponieważ przeprowadzałoby niepotrzebną aktualizację. Wydaje się to marnotrawstwem, ale nie jestem pewien. Każdy z nich powinien działać. Zastanawiam się, czy ktoś wie, co jest najlepsze.
Thomas G Henry,
6
NTuplip - to rozwiązanie jest nadal otwarte na warunki wyścigu od wstawek przez równoczesne transakcje.
Chris KL
REPLACEusuwa wszystkie wiersze w tabeli, dopasowując dowolny klucz PRIMARYlub UNIQUE, a następnie INSERTs . Jest to potencjalnie o wiele więcej pracy niż IODKU.
Rick James
4

Potencjalne niebezpieczeństwo INSERT IGNORE. Jeśli próbujesz wstawić wartość VARCHAR dłużej, wówczas kolumna została zdefiniowana za pomocą - wartość zostanie obcięta i wstawiona NAWET JEŚLI włączony jest tryb ścisły.

lol
źródło
3

W przypadku korzystania insert ignoreposiadające SHOW WARNINGS;oświadczenie na końcu zestawu zapytań pokaże tabelę ze wszystkimi ostrzeżeniami, w tym identyfikatory, które były duplikatami.

Ray Foss
źródło
SHOW WARNINGS;wydaje się mieć wpływ tylko na ostatnie zapytanie. Wszelkie poprzednie wyciągi nie są kumulowane, jeśli masz więcej niż jedno wyciągi.
Kawu
2

Jeśli chcesz wstawić do tabeli i w przypadku konfliktu klucza podstawowego lub indeksu unikalnego, zaktualizuje on wiersz powodujący konflikt zamiast wstawiania tego wiersza.

Składnia:

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

Teraz tutaj instrukcja wstawiania może wyglądać inaczej niż wcześniej. Ta instrukcja wstawiania próbuje wstawić wiersz w tabeli 1 o wartości aib odpowiednio w kolumnie kolumna 1 i kolumna 2.

Dogłębnie zrozummy to stwierdzenie:

Na przykład: tutaj kolumna 1 jest zdefiniowana jako klucz podstawowy w tabeli 1.

Teraz, jeśli w tabeli 1 nie ma wiersza o wartości „a” w kolumnie 1. Tak więc ta instrukcja wstawi wiersz do tabeli1.

Teraz, jeśli w tabeli 1 znajduje się wiersz o wartości „a” w kolumnie 2. Zatem ta instrukcja zaktualizuje wartość kolumny 2 wiersza o „c”, gdzie wartość kolumny 1 to „a”.

Więc jeśli chcesz wstawić nowy wiersz, w przeciwnym razie zaktualizuj ten wiersz o konflikcie klucza podstawowego lub unikalnego indeksu.
Przeczytaj więcej na ten link

Dilraj Singh
źródło
0

INSERT...ON DUPLICATE KEY UPDATE jest preferowany, aby zapobiec nieoczekiwanemu zarządzaniu wyjątkami.

To rozwiązanie działa tylko wtedy, gdy masz ** 1 unikalne ograniczenie **

W moim przypadku wiem o tym col1i col2tworzę unikalny indeks złożony.

Śledzi błąd, ale nie zgłasza wyjątku na duplikacie. Jeśli chodzi o wydajność, aktualizacja o tej samej wartości jest skuteczna, ponieważ MySQL to zauważa i nie aktualizuje

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

Pomysł zastosowania tego podejścia zrodził się z komentarzy na phpdelusions.net/pdo .

micaball
źródło