“ 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.