“ INSERT IGNORE” เทียบกับ“ INSERT … ON DUPLICATE KEY UPDATE”


833

ในขณะที่ดำเนินการINSERTคำสั่งที่มีหลายแถวฉันต้องการข้ามรายการซ้ำที่อาจทำให้เกิดความล้มเหลว หลังจากการวิจัยบางอย่างตัวเลือกของฉันดูเหมือนจะใช้อย่างใดอย่างหนึ่ง:

  • ON DUPLICATE KEY UPDATE ซึ่งแสดงถึงการปรับปรุงที่ไม่จำเป็นโดยเสียค่าใช้จ่ายหรือ
  • INSERT IGNORE ซึ่งแสดงถึงคำเชิญสำหรับความล้มเหลวประเภทอื่น ๆ ที่จะแอบเข้ามาโดยไม่บอกกล่าว

ฉันถูกต้องในสมมติฐานเหล่านี้หรือไม่ อะไรคือวิธีที่ดีที่สุดในการข้ามแถวที่อาจทำให้เกิดการซ้ำซ้อนและไปยังแถวอื่นต่อไป?

คำตอบ:


990

INSERT...ON DUPLICATE KEY UPDATEฉันจะแนะนำให้ใช้

หากคุณใช้งานINSERT IGNOREแถวนั้นจะไม่ถูกแทรกหากผลลัพธ์นั้นมีคีย์ซ้ำ แต่คำสั่งจะไม่สร้างข้อผิดพลาด มันสร้างคำเตือนแทน กรณีเหล่านี้ ได้แก่ :

  • การแทรกคีย์ที่ซ้ำกันในคอลัมน์ด้วยPRIMARY KEYหรือUNIQUEข้อ จำกัด
  • การแทรกค่า NULL ลงในคอลัมน์ด้วยNOT NULLข้อ จำกัด
  • การแทรกแถวไปยังตารางที่แบ่งพาร์ติชันแล้ว แต่ค่าที่คุณแทรกจะไม่จับคู่กับพาร์ติชัน

หากคุณใช้งานREPLACEจริง ๆ แล้ว MySQL จะDELETEตามมาด้วยINSERTภายในซึ่งมีผลข้างเคียงที่ไม่คาดคิด:

  • มีการจัดสรร ID การเพิ่มอัตโนมัติใหม่
  • แถวขึ้นอยู่กับคีย์ต่างประเทศอาจถูกลบออก (ถ้าคุณใช้ cascading คีย์ต่างประเทศ) หรืออื่น ๆ REPLACEที่ป้องกันไม่ให้
  • ทริกเกอร์ที่เปิดไฟDELETEถูกสั่งการโดยไม่จำเป็น
  • ผลข้างเคียงจะแพร่กระจายไปยังแบบจำลองเช่นกัน

การแก้ไข:ทั้งสองREPLACEและINSERT...ON DUPLICATE KEY UPDATEไม่ได้มาตรฐานสิ่งประดิษฐ์ที่เป็นกรรมสิทธิ์เฉพาะกับ MySQL ANSI SQL 2003 กำหนดMERGEคำสั่งที่สามารถแก้ปัญหาความต้องการเดียวกัน (และอื่น ๆ ) แต่ MySQL ไม่สนับสนุนMERGEคำสั่ง


ผู้ใช้พยายามแก้ไขโพสต์นี้ (ผู้ดูแลถูกปฏิเสธ) การแก้ไขพยายามเพิ่มการอ้างสิทธิ์ที่INSERT...ON DUPLICATE KEY UPDATEทำให้มีการจัดสรรรหัสเพิ่มอัตโนมัติใหม่ เป็นความจริงที่รหัสใหม่ถูกสร้างขึ้นแต่ไม่ได้ใช้ในแถวที่เปลี่ยนแปลง

ดูการสาธิตด้านล่างทดสอบกับเซิร์ฟเวอร์ Percona 5.5.28 ตัวแปรการกำหนดค่าinnodb_autoinc_lock_mode=1(ค่าเริ่มต้น):

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

ดังกล่าวข้างต้นแสดงให้เห็นว่าคำสั่ง IODKU uตรวจพบที่ซ้ำกันและเรียกการปรับปรุงเพื่อเปลี่ยนค่าของ สังเกตAUTO_INCREMENT=3ว่าตัวบ่งชี้ id ถูกสร้างขึ้น แต่ไม่ได้ใช้ในแถว

โดยที่REPLACEไม่ลบแถวเดิมและแทรกแถวใหม่สร้างและจัดเก็บรหัสเพิ่มอัตโนมัติใหม่:

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

3
ฉันสงสัยว่าทีมพัฒนา mysql มีความตั้งใจที่จะใช้ MERGE จาก ANSI SQL 2003 หรือไม่?
Lonnie Best

1
@ LonnieBest: คำขอคุณลักษณะสำหรับการนำ MERGE มาใช้ในปี 2005 แต่ไม่มีความคืบหน้าหรือแผนเท่าที่ฉันรู้ bugs.mysql.com/bug.php?id=9018
Bill Karwin

2
โอ้ฉันอาจเพิ่มว่ามันสร้างคำเตือน (ไม่ใช่ข้อผิดพลาด) สำหรับประเภทไม่ตรงกันที่ไม่ถูกต้อง แต่ไม่ได้สร้างคำเตือนสำหรับคีย์หลักคอมโพสิตที่ซ้ำกัน
FabrícioMatté

11
ฉันเพิ่งดูตารางที่มีINSERT ... ON DUPLICATE KEY UPDATE ...ข้อความมากมาย ข้อมูลจำนวนมากซ้ำซ้อนและทำให้ AI AI หนึ่งอินสแตนซ์เพิ่มขึ้นจาก 17,029,941 เป็น 46,271,740 ระหว่างสองแถว AI รุ่นใหม่ทุกครั้งนั้นหมายความว่าช่วงของคุณสามารถเติมเต็มได้อย่างรวดเร็วมากและคุณต้องทำความสะอาด ตารางนี้มีอายุเพียงสองสัปดาห์!
Engineer81

4
@ AntTheKnee อ่าความท้าทายในการทำงานในช่วงเวลาของ Big Data
Bill Karwin

174

ในกรณีที่คุณต้องการเห็นความหมายของสิ่งทั้งหมดนี่คือการระเบิดของทุกสิ่ง:

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

คีย์หลักขึ้นอยู่กับทั้งสองคอลัมน์ของตารางอ้างอิงด่วนนี้ คีย์หลักต้องการค่าที่ไม่ซ้ำกัน

เอาล่ะ:

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

หมายเหตุงานพิเศษด้านบนที่บันทึกไว้มากเกินไปโดยการตั้งค่าคอลัมน์ให้เท่ากับตัวเองไม่จำเป็นต้องทำการอัปเดต

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

และตอนนี้มีการทดสอบหลายแถว:

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

ไม่มีข้อความอื่น ๆ ที่ถูกสร้างขึ้นในคอนโซลและตอนนี้มันมี 4 ค่าเหล่านั้นในข้อมูลตาราง ฉันลบทุกอย่างยกเว้น (1,1) ดังนั้นฉันสามารถทดสอบได้จากสนามเด็กเล่นเดียวกัน

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

ดังนั้นคุณมีมัน เนื่องจากทั้งหมดนี้ดำเนินการบนโต๊ะสดโดยแทบไม่มีข้อมูลและไม่ได้อยู่ในการผลิตเวลาในการดำเนินการจึงเป็นเรื่องเล็กและไม่เกี่ยวข้อง ทุกคนที่มีข้อมูลในโลกแห่งความเป็นจริงจะยินดีมากกว่าที่จะมีส่วนร่วม


ฉันรันทั้งคู่ด้วยรหัสซ้ำและแทนที่ด้วย ตารางของฉันลงท้ายด้วยแถว ~ 120K โดยประมาณ 30% ของแถวของฉันซ้ำซ้อน ในคีย์ที่ซ้ำกันวิ่งใน 102 วินาทีและแทนที่วิ่งใน 105 วินาที สำหรับกรณีของฉันฉันใช้คีย์ซ้ำกัน
crunkchitis

1
ผ่านการทดสอบข้างต้นด้วย MariaDB 10 INSERT IGNORE INTO users_partners (uid,pid) VALUES (1,1),(1,2),(1,3),(1,4)และไม่ได้รับการเตือนเมื่อทำงาน
Floris

คุณใช้ MySQL รุ่นใดในการทำสิ่งนี้?
Radu Murzea

41

สิ่งที่สำคัญที่ต้องเพิ่ม: เมื่อใช้ INSERT IGNORE และคุณมีการละเมิดหลัก ๆ MySQL จะไม่ส่งสัญญาณเตือน!

หากคุณพยายามแทรกอินสแตนซ์ 100 เรคคอร์ดในแต่ละครั้งโดยมีข้อผิดพลาดอันใดอันหนึ่งคุณจะเข้าสู่โหมดโต้ตอบ:

Query OK, 99 rows affected (0.04 sec)

Records: 100 Duplicates: 1 Warnings: 0

ตามที่คุณเห็น: ไม่มีคำเตือน! พฤติกรรมนี้แม้จะอธิบายอย่างผิดพลาดในเอกสาร Mysql อย่างเป็นทางการ

หากจำเป็นต้องแจ้งสคริปต์ของคุณหากยังไม่ได้เพิ่มบางระเบียน (เนื่องจากการละเมิดคีย์) คุณจะต้องเรียก mysql_info () และแยกวิเคราะห์เพื่อหาค่า "ซ้ำ"


6
หากคุณใช้ PHP คุณจะต้องใช้mysqli_affected_rows()เพื่อทราบว่าINSERTเกิดขึ้นจริงหรือไม่
Amal Murali

กับทั้ง MySQL 5.5 และ MariaDB 10 ฉันทำข้อผิดพลาดCannot add or update a child row: a foreign key constraint fails และไม่มีแถว (แม้แต่คนที่ถูกต้อง) มีการเพิ่ม
Floris

2
@Floris ข้อผิดพลาดที่เกิดจากการจำกัด ที่สำคัญต่างประเทศและไม่ได้เกิดจากการคีย์ซ้ำ ฉันใช้ MySQL 5.5.28 เมื่อใช้INSERT IGNOREงานคีย์ที่ซ้ำกันจะถูกละเว้นโดยไม่มีข้อผิดพลาดหรือคำเตือน
toxalot

20

ฉันใช้เป็นประจำINSERT IGNOREและฟังดูเหมือนว่าเป็นพฤติกรรมที่คุณกำลังมองหาเช่นกัน ตราบใดที่คุณรู้ว่าแถวที่จะทำให้เกิดความขัดแย้งของดัชนีจะไม่ถูกแทรกและคุณวางแผนโปรแกรมของคุณตามนั้นมันไม่ควรทำให้เกิดปัญหาใด ๆ


4
ฉันกังวลว่าฉันจะละเว้นข้อผิดพลาดอื่น ๆ นอกเหนือจากการทำซ้ำ สิ่งนี้ถูกต้องหรือไม่ INSERT IGNORE จะเพิกเฉยต่อความล้มเหลวในการทำซ้ำเท่านั้น ขอบคุณ!
โทมัสเฮนรี่ G

2
มันจะเปลี่ยนข้อผิดพลาดใด ๆ ให้เป็นคำเตือน ดูรายการกรณีดังกล่าวในคำตอบของฉัน
Bill Karwin

นั่นเป็นความอัปยศ ฉันหวังว่ามันจะเพิกเฉยต่อความล้มเหลวซ้ำซ้อนเท่านั้น
Lonnie Best

การละเมิดที่สำคัญทำให้เกิดข้อผิดพลาด ! ดูความคิดเห็นของฉันที่คำตอบของ @Jens
Floris

1
@Pacerier ขึ้นอยู่กับว่าแอปพลิเคชันของคุณตรวจสอบคำเตือนหรือไม่ หรือถ้ามันสามารถตรวจสอบคำเตือน ตัวอย่างเช่นแพคเกจ ORM ส่วนใหญ่ไม่ได้ให้โอกาสคุณ ตัวเชื่อมต่อบางตัว (เช่น JDBC) แยกคุณออกจาก MySQL API ด้วยดังนั้นคุณจะไม่ได้รับโอกาสตรวจสอบคำเตือน
Bill Karwin

18

ฉันรู้ว่านี่เก่า แต่ฉันจะเพิ่มบันทึกย่อนี้ในกรณีที่มีคนอื่น (เช่นฉัน) มาที่หน้านี้ในขณะที่พยายามค้นหาข้อมูลใน INSERT .. SignORE

ตามที่กล่าวไว้ข้างต้นหากคุณใช้ INSERT .. SignORE ข้อผิดพลาดที่เกิดขึ้นขณะดำเนินการคำสั่ง INSERT จะถือเป็นคำเตือนแทน

สิ่งหนึ่งที่ไม่ได้กล่าวถึงอย่างชัดเจนคือ INSERT .. SignORE จะทำให้ค่าที่ไม่ถูกต้องจะถูกปรับเป็นค่าที่ใกล้เคียงที่สุดเมื่อแทรก (ในขณะที่ค่าที่ไม่ถูกต้องจะทำให้แบบสอบถามยกเลิกหากไม่ใช้คำหลัก IGNORE)


6
ฉันไม่แน่ใจว่าสิ่งที่คุณหมายถึงโดย "ค่าไม่ถูกต้อง" และแก้ไขอะไร คุณสามารถให้ตัวอย่างหรือคำอธิบายเพิ่มเติมได้หรือไม่?
Marenz

4
หมายความว่าหากคุณแทรกประเภทข้อมูลที่ไม่ถูกต้องลงในเขตข้อมูลเมื่อใช้ "INSERT IGNORE" ข้อมูลจะถูกปรับเปลี่ยนให้ตรงกับประเภทข้อมูลของเขตข้อมูลและจะใส่ค่าที่ไม่ถูกต้องจากนั้นแบบสอบถามจะทำงานต่อไป ด้วย "INSERT" เท่านั้นข้อผิดพลาดจะถูกยกขึ้นเกี่ยวกับชนิดข้อมูลที่ไม่ถูกต้องและแบบสอบถามจะถูกยกเลิก สิ่งนี้อาจตกลงกับหมายเลขที่ถูกแทรกลงใน varchar หรือฟิลด์ข้อความ แต่การแทรกสตริงข้อความลงในฟิลด์ที่มีชนิดข้อมูลตัวเลขจะส่งผลให้ข้อมูลไม่ถูกต้อง
codewaggle

2
@Marenz อีกตัวอย่าง: หากตารางของคุณมีคอลัมน์ที่ไม่เป็น null และแบบสอบถาม "INSERT IGNORE" ของคุณไม่ได้ระบุค่าสำหรับคอลัมน์นั้นแถวจะถูกแทรกด้วยค่าศูนย์ในคอลัมน์นั้นโดยไม่คำนึงว่าจะเปิดใช้งาน sql_mode ที่เข้มงวดหรือไม่ .
แชนนอน

จุดดีเกี่ยวกับค่าที่ไม่ถูกต้อง! หัวข้อนี้เหมาะสำหรับการเรียนรู้เกี่ยวกับ "INSERT IGNORE" ฉันจะปล่อยให้ 5 เซนต์ของฉันด้วย: medium.com/legacy-systems-diary/ …บทความที่ดีพร้อมตัวอย่างเกี่ยวกับความระมัดระวังที่คุณควรใช้ในขณะที่ใช้ "INSERT IGNORE" คำให้การ.
0x49D1

8

ON ซ้ำ KEY UPDATE ไม่ได้จริงๆในมาตรฐาน มันเกี่ยวกับมาตรฐานเท่าที่ REPLACE เป็น ดูSQL ผสาน

โดยพื้นฐานแล้วทั้งสองคำสั่งเป็นเวอร์ชันทางเลือกไวยากรณ์ของคำสั่งมาตรฐาน


1
แทนที่จะลบและแทรกในขณะที่ปรับปรุงคีย์ onduplicate ปรับปรุงแถวที่มีอยู่ ความแตกต่างบางประการคือ: id ที่เพิ่มขึ้นอัตโนมัติ, ตำแหน่งแถว, กลุ่มของทริกเกอร์
ahnbizcad

8

Replaceดูเหมือนว่าตัวเลือก หรือคุณสามารถตรวจสอบด้วย

IF NOT EXISTS(QUERY) Then INSERT

สิ่งนี้จะแทรกหรือลบจากนั้นแทรก ฉันมักจะไปIF NOT EXISTSตรวจสอบก่อน


ขอบคุณสำหรับการตอบกลับอย่างรวดเร็ว ฉันถือว่าทั่วทุกสถานที่ แต่ฉันคิดว่านี่จะคล้ายกับ ON DUPLICATE KEY UPDATE ซึ่งมันจะทำการอัปเดตที่ไม่จำเป็น ดูเหมือนจะสิ้นเปลือง แต่ฉันไม่แน่ใจ สิ่งเหล่านี้ควรได้ผล ฉันสงสัยว่าถ้าใครรู้ว่าใครดีที่สุด
Thomas Thomas Henry

6
NTuplip - โซลูชันนั้นยังคงเปิดให้มีเงื่อนไขการแข่งขันจากส่วนแทรกโดยธุรกรรมที่เกิดขึ้นพร้อมกัน
Chris KL

REPLACEลบทุกแถวในตารางกับการจับคู่ใด ๆ PRIMARYหรือUNIQUEที่สำคัญแล้ว INSERTsนี่อาจเป็นงานอีกมากที่ IODKU
Rick James

4

อันตรายที่อาจเกิดขึ้นจาก INSERT IGNORE หากคุณกำลังพยายามที่จะแทรกค่า VARCHAR อีกต่อไปแล้วคอลัมน์ถูกกำหนดด้วย - ค่าจะถูกตัดทอนและแทรกแม้ว่าจะเปิดใช้งานโหมดเข้มงวด


3

ถ้าใช้insert ignoreมีSHOW WARNINGS;คำสั่งในตอนท้ายของการตั้งค่าการค้นหาของคุณจะแสดงตารางที่มีคำเตือนทั้งหมดรวมทั้งที่มีรหัสที่ซ้ำกัน


SHOW WARNINGS;ดูเหมือนว่าจะส่งผลต่อการค้นหาล่าสุดเท่านั้น คำสั่งก่อนหน้าใด ๆ จะไม่ถูกสะสมหากคุณมีมากกว่าหนึ่งคำสั่ง
Kawu

2

หากคุณต้องการแทรกในตารางและในความขัดแย้งของคีย์หลักหรือดัชนีที่ไม่ซ้ำกันมันจะอัปเดตแถวที่ขัดแย้งกันแทนการแทรกแถวนั้น

ไวยากรณ์:

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

ตอนนี้ที่นี่คำสั่งแทรกอาจดูแตกต่างจากที่คุณเห็นก่อนหน้านี้ คำสั่งแทรกนี้พยายามที่จะแทรกแถวใน table1 ด้วยค่าของ a และ b ลงในคอลัมน์ column1 และ column2 ตามลำดับ

มาทำความเข้าใจกับข้อความนี้ในเชิงลึก:

ตัวอย่างเช่น: here column1 ถูกกำหนดเป็นคีย์หลักใน table1

ตอนนี้ถ้าใน table1 ไม่มีแถวที่มีค่า“ a” ในคอลัมน์ 1 ดังนั้นคำสั่งนี้จะแทรกแถวใน table1

ตอนนี้ถ้าใน table1 มีแถวที่มีค่า“ a” ในคอลัมน์ 2 ดังนั้นคำสั่งนี้จะอัพเดทค่า column2 ของแถวด้วย“ c” โดยที่ค่า column1 คือ“ a”

ดังนั้นหากคุณต้องการแทรกแถวใหม่มิฉะนั้นให้อัปเดตแถวนั้นกับความขัดแย้งของคีย์หลักหรือดัชนีที่ไม่ซ้ำกัน
อ่านเพิ่มเติมได้ที่ลิงค์นี้


0

INSERT...ON DUPLICATE KEY UPDATE มีวัตถุประสงค์เพื่อป้องกันการจัดการข้อยกเว้นที่ไม่คาดคิด

โซลูชันนี้ใช้ได้เมื่อคุณมีข้อ จำกัด ที่ไม่ซ้ำกัน ** 1 เท่านั้น

ในกรณีของฉันฉันรู้col1และcol2สร้างดัชนีคอมโพสิตที่ไม่ซ้ำกัน

มันติดตามข้อผิดพลาด แต่ไม่ทิ้งข้อยกเว้นในการทำซ้ำ เกี่ยวกับประสิทธิภาพการอัพเดตด้วยค่าเดียวกันนั้นมีประสิทธิภาพเมื่อMySQL สังเกตเห็นและไม่อัพเดท

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

ความคิดที่จะใช้วิธีการนี้มาจากความคิดเห็นที่phpdelusions.net/pdo

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.