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


833

Saat menjalankan INSERTpernyataan dengan banyak baris, saya ingin melewatkan entri duplikat yang jika tidak akan menyebabkan kegagalan. Setelah beberapa penelitian, opsi saya tampaknya adalah penggunaan:

  • ON DUPLICATE KEY UPDATE yang menyiratkan pembaruan yang tidak perlu dengan biaya tertentu, atau
  • INSERT IGNORE yang menyiratkan undangan untuk jenis kegagalan lain untuk masuk tanpa pemberitahuan.

Apakah saya benar dalam asumsi-asumsi ini? Apa cara terbaik untuk hanya melewatkan baris yang dapat menyebabkan duplikat dan melanjutkan ke baris lainnya?

Jawaban:


991

Saya akan merekomendasikan menggunakan INSERT...ON DUPLICATE KEY UPDATE.

Jika Anda menggunakan INSERT IGNORE, maka baris tersebut tidak akan benar-benar dimasukkan jika menghasilkan kunci duplikat. Tetapi pernyataan itu tidak akan menghasilkan kesalahan. Alih-alih menghasilkan peringatan. Kasus-kasus ini meliputi:

  • Memasukkan kunci duplikat dalam kolom dengan PRIMARY KEYatau UNIQUEkendala.
  • Memasukkan NULL ke dalam kolom dengan NOT NULLbatasan.
  • Menyisipkan baris ke tabel dipartisi, tetapi nilai yang Anda masukkan tidak memetakan ke partisi.

Jika Anda menggunakan REPLACE, MySQL sebenarnya DELETEdiikuti oleh INSERTinternal, yang memiliki beberapa efek samping yang tidak terduga:

  • ID kenaikan-otomatis baru dialokasikan.
  • Baris bergantung dengan kunci asing dapat dihapus (jika Anda menggunakan kunci asing mengalir) atau mencegah REPLACE .
  • Pemicu yang menyala DELETEdijalankan tidak perlu.
  • Efek samping juga disebarkan ke replika.

koreksi: keduanya REPLACEdan INSERT...ON DUPLICATE KEY UPDATEnon-standar, penemuan eksklusif untuk MySQL. ANSI SQL 2003 mendefinisikan MERGEpernyataan yang dapat menyelesaikan kebutuhan yang sama (dan banyak lagi), tetapi MySQL tidak mendukung MERGEpernyataan itu.


Seorang pengguna mencoba mengedit posting ini (hasil edit ditolak oleh moderator). Hasil edit mencoba menambahkan klaim yang INSERT...ON DUPLICATE KEY UPDATEmenyebabkan id penambahan otomatis baru dialokasikan. Benar bahwa id baru dibuat , tetapi tidak digunakan di baris yang diubah.

Lihat demonstrasi di bawah, diuji dengan Percona Server 5.5.28. Variabel konfigurasi innodb_autoinc_lock_mode=1(default):

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

Di atas menunjukkan bahwa pernyataan IODKU mendeteksi duplikat, dan meminta pembaruan untuk mengubah nilai u. Perhatikan AUTO_INCREMENT=3menunjukkan bahwa id dihasilkan, tetapi tidak digunakan di baris.

Sedangkan REPLACEmenghapus baris asli dan menyisipkan baris baru, menghasilkan dan menyimpan id kenaikan otomatis baru:

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

3
Saya ingin tahu apakah tim pengembangan mysql memiliki niat untuk mengadopsi MERGE dari ANSI SQL 2003?
Lonnie Best

1
@LonnieBest: Permintaan fitur untuk mengimplementasikan MERGE dibuat pada tahun 2005, tetapi sejauh ini tidak ada kemajuan atau rencana. bugs.mysql.com/bug.php?id=9018
Bill Karwin

2
Oh, saya dapat menambahkan bahwa itu menghasilkan peringatan (bukan kesalahan) untuk ketidakcocokan tipe tidak valid tetapi tidak menghasilkan peringatan untuk duplikat kunci primer gabungan.
Fabrício Matté

11
Saya baru saja melihat tabel yang telah diisi oleh banyak INSERT ... ON DUPLICATE KEY UPDATE ...pernyataan. Banyak data duplikat, dan telah menghasilkan satu contoh dari PK AI meningkat dari 17.029.941 menjadi 46.271.740 antara dua baris. Generasi AI baru setiap kali berarti jangkauan Anda dapat dengan sangat cepat diisi dan Anda perlu membersihkannya. Meja ini baru berumur dua minggu!
Insinyur81

4
@AntTheKnee, ahh, tantangan bekerja di saat Big Data.
Bill Karwin

174

Jika Anda ingin melihat apa arti semua ini, berikut adalah pukulan dari segalanya:

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

Kunci primer didasarkan pada kedua kolom dari tabel referensi cepat ini. Kunci primer memerlukan nilai unik.

Mari kita mulai:

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

perhatikan, di atas menyimpan terlalu banyak pekerjaan tambahan dengan mengatur kolom sama dengan dirinya sendiri, tidak ada pembaruan yang benar-benar diperlukan

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

dan sekarang beberapa tes baris:

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

tidak ada pesan lain yang dihasilkan di konsol, dan sekarang ada 4 nilai di tabel data. Saya menghapus semuanya kecuali (1,1) sehingga saya bisa menguji dari lapangan bermain yang sama

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

Jadi begitulah. Karena ini semua dilakukan di atas meja baru dengan hampir tidak ada data dan tidak dalam produksi, waktu pelaksanaannya adalah mikroskopis dan tidak relevan. Siapa pun dengan data dunia nyata akan lebih dari senang untuk berkontribusi.


Saya menjalankan keduanya pada kunci duplikat dan ganti menjadi. Tabel saya berakhir dengan ~ 120K baris dengan sekitar 30% dari baris saya menjadi duplikat. Pada kunci duplikat berlari dalam 102 detik dan ganti berlari dalam 105 detik. Untuk kasus saya, saya menggunakan kunci duplikat.
crunkchitis

1
Diuji di atas dengan MariaDB 10 dan tidak mendapatkan peringatan saat berjalan INSERT IGNORE INTO users_partners (uid,pid) VALUES (1,1),(1,2),(1,3),(1,4).
Floris

Versi MySQL apa yang Anda gunakan untuk semua ini?
Radu Murzea

41

Sesuatu yang penting untuk ditambahkan: Ketika menggunakan INSERT IGNORE dan Anda memang memiliki pelanggaran utama, MySQL TIDAK memunculkan peringatan!

Jika Anda mencoba misalnya untuk memasukkan 100 catatan sekaligus, dengan satu catatan yang salah, Anda akan mendapatkan dalam mode interaktif:

Query OK, 99 rows affected (0.04 sec)

Records: 100 Duplicates: 1 Warnings: 0

Seperti yang Anda lihat: Tidak Ada Peringatan! Perilaku ini bahkan salah dijelaskan dalam Dokumentasi Mysql resmi.

Jika skrip Anda perlu diinformasikan, jika beberapa catatan belum ditambahkan (karena pelanggaran utama), Anda harus memanggil mysql_info () dan menguraikannya untuk nilai "Duplikat".


6
Jika Anda menggunakan PHP, Anda harus menggunakannya mysqli_affected_rows()untuk mengetahui apakah itu INSERTbenar - benar terjadi.
Amal Murali

Dengan kedua MySQL 5.5 dan MariaDB 10 saya lakukan mendapatkan error Cannot add or update a child row: a foreign key constraint fails dan tidak ada baris (bahkan yang valid) ditambahkan.
Floris

2
@ Floris Kesalahan itu disebabkan oleh batasan kunci asing dan bukan karena kunci duplikat . Saya menggunakan MySQL 5.5.28. Saat menggunakan INSERT IGNORE, kunci duplikat diabaikan tanpa kesalahan atau peringatan.
toxalot

20

Saya secara rutin menggunakan INSERT IGNORE, dan sepertinya jenis perilaku yang Anda cari juga. Selama Anda tahu bahwa baris yang akan menyebabkan konflik indeks tidak akan dimasukkan dan Anda merencanakan program Anda sesuai, itu seharusnya tidak menyebabkan masalah.


4
Saya khawatir bahwa saya akan mengabaikan kesalahan selain duplikasi. Apakah ini benar atau apakah INSERT IGNORE hanya mengabaikan saja mengabaikan kegagalan duplikasi? Terima kasih!
Thomas G Henry

2
Itu mengubah kesalahan menjadi peringatan. Lihat daftar kasus seperti itu dalam jawaban saya.
Bill Karwin

Itu memalukan; Saya berharap itu hanya akan mengabaikan kegagalan duplikat.
Lonnie Best

Pelanggaran utama memang menyebabkan kesalahan ! Lihat komentar saya di jawaban @Jens '.
Floris

1
@Pacerier, tergantung pada apakah aplikasi Anda memeriksa peringatan. Atau apakah bisa mengecek peringatan. Misalnya, sebagian besar paket ORM tidak memberi Anda kesempatan. Beberapa konektor (misalnya JDBC) juga memisahkan Anda dari API MySQL sehingga Anda tidak mendapatkan kesempatan untuk memeriksa peringatan.
Bill Karwin

18

Saya tahu ini sudah tua, tetapi saya akan menambahkan catatan ini jika ada orang lain (seperti saya) tiba di halaman ini ketika mencoba mencari informasi di INSERT..IGNORE.

Seperti disebutkan di atas, jika Anda menggunakan INSERT..IGNORE, kesalahan yang terjadi saat menjalankan pernyataan INSERT diperlakukan sebagai peringatan.

Satu hal yang tidak disebutkan secara eksplisit adalah bahwa INSERT..IGNORE akan menyebabkan nilai yang tidak valid akan disesuaikan dengan nilai terdekat saat dimasukkan (sedangkan nilai yang tidak valid akan menyebabkan permintaan dibatalkan jika kata kunci IGNORE tidak digunakan).


6
Saya tidak begitu yakin apa yang Anda maksud dengan "nilai tidak valid" dan dikoreksi untuk apa? Bisakah Anda memberikan contoh atau penjelasan lebih lanjut?
Marenz

4
Ini berarti bahwa jika Anda memasukkan tipe data yang salah ke dalam bidang saat menggunakan "INSERT IGNORE", data akan dimodifikasi agar sesuai dengan tipe data bidang tersebut dan nilai yang berpotensi tidak valid akan dimasukkan, maka kueri akan terus berjalan. Dengan "INSERT" saja, kesalahan akan dimunculkan tentang tipe data yang salah dan kueri akan dibatalkan. Ini mungkin OK dengan angka yang dimasukkan ke bidang varchar atau teks, tetapi memasukkan string teks ke dalam bidang dengan tipe data numerik akan menghasilkan data yang buruk.
codewaggle

2
@Marenz contoh lain: jika tabel Anda memiliki kolom non-nol dan kueri "INSERT IGNORE" Anda tidak menentukan nilai untuk kolom itu, baris akan dimasukkan dengan nilai nol di kolom itu terlepas dari apakah sql_mode ketat diaktifkan. .
Shannon

Poin bagus tentang nilai tidak valid! Utas ini sangat bagus untuk mempelajari tentang "INSERT IGNORE", saya akan meninggalkan 5 sen saya juga: medium.com/legacy-systems-diary/… artikel yang bagus dengan contoh-contoh tentang seberapa hati-hati Anda seharusnya saat menggunakan "INSERT IGNORE" pernyataan.
0x49D1

8

DI UPDATE KUNCI DUPLICATE tidak benar - benar dalam standar. Ini tentang standar seperti REPLACE. Lihat SQL MERGE .

Pada dasarnya kedua perintah adalah versi sintaks alternatif dari perintah standar.


1
ganti menghapus dan menyisipkan, sedangkan pembaruan kunci onduplicate memperbarui baris yang ada. beberapa perbedaan adalah: id kenaikan otomatis, posisi baris, banyak pemicu
ahnbizcad

8

ReplaceSepertinya pilihan. Atau Anda bisa memeriksanya

IF NOT EXISTS(QUERY) Then INSERT

Ini akan menyisipkan atau menghapus lalu menyisipkan. Saya cenderung pergi untuk IF NOT EXISTScek dulu.


Terima kasih atas balasan cepatnya. Saya berasumsi di semua tempat, tapi saya menganggap ini akan mirip dengan ON DUPLICATE KEY UPDATE karena akan melakukan pembaruan yang tidak perlu. Tampaknya boros, tapi saya tidak yakin. Semua ini harus bekerja. Saya bertanya-tanya apakah ada yang tahu yang terbaik.
Thomas G Henry

6
NTuplip - solusi itu masih terbuka untuk kondisi balapan dari sisipan dengan transaksi bersamaan.
Chris KL

REPLACEmenghapus semua baris dalam tabel dengan mencocokkan tombol atau apa pun , lalu . Ini berpotensi lebih banyak pekerjaan daripada IODKU. PRIMARYUNIQUE INSERTs
Rick James

4

Bahaya potensial INSERT IGNORE. Jika Anda mencoba memasukkan nilai VARCHAR lebih lama dari itu kolom didefinisikan dengan - nilai akan dipotong dan disisipkan BAHKAN JIKA mode ketat diaktifkan.


3

Jika menggunakan insert ignorememiliki SHOW WARNINGS;pernyataan di akhir set kueri Anda akan menampilkan tabel dengan semua peringatan, termasuk ID mana yang merupakan duplikat.


SHOW WARNINGS;sepertinya hanya memengaruhi kueri terbaru. Pernyataan sebelumnya tidak diakumulasi, jika Anda memiliki lebih dari satu pernyataan.
Kawu

2

Jika Anda ingin menyisipkan dalam tabel dan pada konflik kunci utama atau indeks unik itu akan memperbarui baris yang bertentangan daripada memasukkan baris itu.

Sintaksis:

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

Sekarang di sini, pernyataan penyisipan ini mungkin terlihat berbeda dari yang Anda lihat sebelumnya. Pernyataan penyisipan ini mencoba menyisipkan baris dalam table1 dengan nilai a dan b ke dalam kolom1 dan kolom2 masing-masing.

Mari kita memahami pernyataan ini secara mendalam:

Sebagai contoh: di sini kolom1 didefinisikan sebagai kunci utama dalam tabel1.

Sekarang jika dalam table1 tidak ada baris yang memiliki nilai "a" di kolom1. Jadi pernyataan ini akan menyisipkan baris di table1.

Sekarang jika dalam table1 ada baris yang memiliki nilai "a" di kolom2. Jadi pernyataan ini akan memperbarui nilai kolom2 baris dengan "c" di mana nilai column1 adalah "a".

Jadi, jika Anda ingin menyisipkan baris baru, perbarui baris tersebut pada konflik kunci utama atau indeks unik.
Baca lebih lanjut tentang tautan ini


0

INSERT...ON DUPLICATE KEY UPDATE lebih disukai untuk mencegah manajemen Pengecualian yang tidak terduga.

Solusi ini berfungsi saat Anda memiliki ** 1 batasan unik ** saja

Dalam kasus saya, saya tahu itu col1dancol2 membuat indeks komposit yang unik.

Ini melacak kesalahan, tetapi tidak melempar pengecualian pada duplikat. Mengenai kinerja, pembaruan dengan nilai yang sama efisien karena pemberitahuan MySQL ini dan tidak memperbaruinya

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

Gagasan untuk menggunakan pendekatan ini berasal dari komentar di phpdelusions.net/pdo .

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.