CÂU CHUYỆN CÂU CHUYỆN CÂU CHUYỆN


833

Trong khi thực hiện một INSERTcâu lệnh có nhiều hàng, tôi muốn bỏ qua các mục trùng lặp có thể gây ra lỗi. Sau một số nghiên cứu, các tùy chọn của tôi dường như là sử dụng một trong hai:

  • ON DUPLICATE KEY UPDATE trong đó ngụ ý một bản cập nhật không cần thiết với một số chi phí, hoặc
  • INSERT IGNORE trong đó ngụ ý một lời mời cho các loại thất bại khác không được thông báo.

Tôi có đúng trong những giả định này không? Cách tốt nhất để bỏ qua các hàng có thể gây trùng lặp và tiếp tục đến các hàng khác là gì?

Câu trả lời:


991

Tôi khuyên bạn nên sử dụng INSERT...ON DUPLICATE KEY UPDATE.

Nếu bạn sử dụng INSERT IGNORE, thì hàng thực sự sẽ không được chèn nếu nó dẫn đến một khóa trùng lặp. Nhưng tuyên bố sẽ không tạo ra một lỗi. Nó tạo ra một cảnh báo thay thế. Những trường hợp này bao gồm:

  • Chèn một khóa trùng lặp trong các cột có PRIMARY KEYhoặc UNIQUEràng buộc.
  • Chèn một NULL vào một cột có NOT NULLràng buộc.
  • Chèn một hàng vào bảng được phân đoạn, nhưng các giá trị bạn chèn không ánh xạ vào phân vùng.

Nếu bạn sử dụng REPLACE, MySQL thực sự làm DELETEtheo sau bởi INSERTnội bộ, có một số tác dụng phụ không mong muốn:

  • ID tăng tự động mới được phân bổ.
  • Các hàng phụ thuộc có khóa ngoại có thể bị xóa (nếu bạn sử dụng xếp chồng khóa ngoại) hoặc nếu không thì ngăn chặn REPLACE.
  • Kích hoạt mà bắn vào DELETEđược thực hiện không cần thiết.
  • Tác dụng phụ được truyền đến bản sao quá.

sửa chữa: cả hai REPLACEINSERT...ON DUPLICATE KEY UPDATEđều là tiêu chuẩn, phát minh độc quyền dành riêng cho MySQL. ANSI SQL 2003 định nghĩa một MERGEcâu lệnh có thể giải quyết cùng một nhu cầu (và hơn thế nữa), nhưng MySQL không hỗ trợ MERGEcâu lệnh đó.


Một người dùng đã cố gắng chỉnh sửa bài đăng này (bản chỉnh sửa đã bị người kiểm duyệt từ chối). Việc chỉnh sửa đã cố gắng thêm một khiếu nại INSERT...ON DUPLICATE KEY UPDATEkhiến id tăng tự động mới được phân bổ. Đúng là id mới được tạo , nhưng nó không được sử dụng trong hàng đã thay đổi.

Xem trình diễn bên dưới, được thử nghiệm với Percona Server 5.5.28. Biến cấu hình innodb_autoinc_lock_mode=1(mặc định):

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

Những điều trên chứng tỏ rằng câu lệnh IODKU phát hiện bản sao và gọi bản cập nhật để thay đổi giá trị của u. Lưu ý AUTO_INCREMENT=3chỉ ra một id đã được tạo, nhưng không được sử dụng trong hàng.

Trong khi đó REPLACE, xóa hàng ban đầu và chèn một hàng mới, tạo lưu trữ id tăng tự động mới:

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

3
Tôi tự hỏi liệu nhóm phát triển mysql có ý định bao giờ áp dụng MERGE từ ANSI SQL 2003 không?
Lonnie hay nhất

1
@LonnieBest: Yêu cầu tính năng để triển khai MERGE được thực hiện vào năm 2005, nhưng không có tiến triển hay kế hoạch nào theo như tôi biết. bug.mysql.com/orms.php?id=9018
Bill Karwin

2
Ồ tôi có thể thêm rằng nó tạo ra các cảnh báo (không phải lỗi) cho kiểu không khớp không hợp lệ nhưng nó không tạo ra cảnh báo cho khóa chính tổng hợp trùng lặp.
Fabrício Matté

11
Tôi vừa mới nhìn vào một cái bàn đã được đưa ra bởi rất nhiều INSERT ... ON DUPLICATE KEY UPDATE ...câu nói. Rất nhiều dữ liệu bị trùng lặp và nó đã dẫn đến một trường hợp AI PK tăng từ 17.029.941 lên 46.271.740 giữa hai hàng. Thế hệ AI mới đó mỗi lần có nghĩa là phạm vi của bạn có thể rất nhanh chóng được lấp đầy và bạn cần phải dọn sạch. Chiếc bàn này chỉ mới hai tuần tuổi!
Kỹ

4
@AntTheKnee, ahh, những thách thức khi làm việc trong thời đại Dữ liệu lớn.
Bill Karwin

174

Trong trường hợp bạn muốn xem tất cả điều này có nghĩa là gì, thì đây là một cú đánh của tất cả mọi thứ:

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

Khóa chính dựa trên cả hai cột của bảng tham chiếu nhanh này. Khóa chính yêu cầu các giá trị duy nhất.

Hãy bắt đầu nào:

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

lưu ý, ở trên đã lưu quá nhiều công việc phụ bằng cách đặt cột bằng chính nó, không thực sự cần cập nhật

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

và bây giờ một số bài kiểm tra nhiều hàng:

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

không có thông báo nào khác được tạo trong bảng điều khiển và hiện có 4 giá trị đó trong dữ liệu bảng. Tôi đã xóa mọi thứ trừ (1,1) để tôi có thể kiểm tra từ cùng một sân chơi

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

Vì vậy, có bạn có nó. Vì tất cả điều này được thực hiện trên một bảng mới với gần như không có dữ liệu và không được sản xuất, nên thời gian thực hiện là rất nhỏ và không liên quan. Bất cứ ai có dữ liệu trong thế giới thực đều được chào đón để đóng góp.


Tôi chạy cả hai trên khóa trùng lặp và thay thế vào. Các bảng của tôi đã kết thúc với ~ 120K hàng với khoảng 30% số hàng của tôi là trùng lặp. Trên khóa trùng lặp chạy trong 102 giây và thay thế chạy trong 105 giây. Đối với trường hợp của tôi, tôi đang sử dụng khóa trùng lặp.
viêm xương khớp

1
Đã thử nghiệm ở trên với MariaDB 10 và đã nhận được cảnh báo khi chạy INSERT IGNORE INTO users_partners (uid,pid) VALUES (1,1),(1,2),(1,3),(1,4).
Floris

Phiên bản MySQL nào bạn đã sử dụng cho tất cả điều này?
Radu Murzea

41

Một điều quan trọng cần thêm: Khi sử dụng INSERT IGNORE và bạn có vi phạm chính, MySQL KHÔNG đưa ra cảnh báo!

Ví dụ: nếu bạn cố gắng chèn 100 bản ghi, với một bản ghi bị lỗi, bạn sẽ nhận được ở chế độ tương tác:

Query OK, 99 rows affected (0.04 sec)

Records: 100 Duplicates: 1 Warnings: 0

Như bạn thấy: Không có cảnh báo! Hành vi này thậm chí được mô tả sai trong Tài liệu Mysql chính thức.

Nếu tập lệnh của bạn cần được thông báo, nếu một số bản ghi chưa được thêm vào (do vi phạm chính), bạn phải gọi mysql_info () và phân tích cú pháp cho giá trị "Bản sao".


6
Nếu bạn đang sử dụng PHP, bạn sẽ cần sử dụng mysqli_affected_rows()để biết nếu điều đó INSERTthực sự xảy ra.
Amal Murali

Với cả MySQL 5.5 và MariaDB 10, tôi đều gặp lỗi Cannot add or update a child row: a foreign key constraint fails và không có hàng nào (kể cả hàng hợp lệ) được thêm vào.
Floris

2
@Floris Lỗi đó là do ràng buộc khóa ngoại và không phải do khóa trùng lặp . Tôi đang sử dụng MySQL 5.5.28. Khi sử dụng INSERT IGNORE, các khóa trùng lặp được bỏ qua không có lỗi hoặc cảnh báo.
toxalot

20

Tôi thường xuyên sử dụng INSERT IGNOREvà có vẻ như chính xác là loại hành vi mà bạn đang tìm kiếm. Miễn là bạn biết rằng các hàng gây ra xung đột chỉ mục sẽ không được chèn và bạn lên kế hoạch cho chương trình của mình, điều đó sẽ không gây ra rắc rối nào.


4
Tôi lo ngại rằng tôi sẽ bỏ qua các lỗi khác ngoài việc sao chép. Điều này có đúng không hay INSERT IGNORE chỉ bỏ qua chỉ bỏ qua lỗi trùng lặp? Cảm ơn!
Thomas G Henry

2
Nó biến bất kỳ lỗi nào thành một cảnh báo. Xem danh sách các trường hợp như vậy trong câu trả lời của tôi.
Bill Karwin

Thật là xấu hổ; Tôi ước nó sẽ chỉ bỏ qua những thất bại trùng lặp.
Lonnie hay nhất

Vi phạm chính gây ra lỗi ! Xem bình luận của tôi tại câu trả lời của @Jens.
Floris

1
@Pacerier, nó phụ thuộc vào việc ứng dụng của bạn có kiểm tra cảnh báo hay không. Hoặc nếu nó có thể kiểm tra cảnh báo. Ví dụ: hầu hết các gói ORM không cung cấp cho bạn cơ hội. Một số trình kết nối (ví dụ JDBC) cũng tách bạn khỏi API MySQL để bạn không có cơ hội kiểm tra cảnh báo.
Bill Karwin

18

Tôi biết cái này đã cũ, nhưng tôi sẽ thêm ghi chú này trong trường hợp bất kỳ ai khác (như tôi) đến trang này trong khi cố gắng tìm thông tin trên INSERT..IGNORE.

Như đã đề cập ở trên, nếu bạn sử dụng INSERT..IGNORE, các lỗi xảy ra trong khi thực hiện câu lệnh INSERT được coi là cảnh báo thay thế.

Một điều không được đề cập rõ ràng là INSERT..IGNORE sẽ gây ra các giá trị không hợp lệ sẽ được điều chỉnh thành các giá trị gần nhất khi được chèn (trong khi các giá trị không hợp lệ sẽ khiến truy vấn hủy bỏ nếu từ khóa IGNORE không được sử dụng).


6
Tôi không thực sự chắc chắn ý của bạn là "giá trị không hợp lệ" và được sửa thành gì? Bạn có thể cung cấp một ví dụ hoặc giải thích thêm?
Marenz

4
Điều đó có nghĩa là nếu bạn chèn loại dữ liệu sai vào một trường khi sử dụng "INSERT IGNORE", dữ liệu sẽ được sửa đổi để khớp với loại dữ liệu của trường và một giá trị không hợp lệ sẽ được chèn, sau đó truy vấn sẽ tiếp tục chạy. Chỉ với "CHERTN", một lỗi sẽ xuất hiện về kiểu dữ liệu không chính xác và truy vấn sẽ bị hủy bỏ. Điều này có thể ổn với một số được chèn vào một trường varchar hoặc văn bản, nhưng chèn một chuỗi văn bản vào một trường có kiểu dữ liệu số sẽ dẫn đến dữ liệu xấu.
mã hóa

2
@Marenz một ví dụ khác: nếu bảng của bạn có cột không null và truy vấn "INSERT IGNORE" của bạn không chỉ định giá trị cho cột đó, hàng sẽ được chèn với giá trị 0 trong cột đó bất kể có bật sql_mode nghiêm ngặt hay không .
Shannon

Điểm tốt về các giá trị không hợp lệ! Chủ đề này là rất tốt cho việc học về "INSERT bỏ qua", tôi sẽ rời khỏi tôi 5 cent quá: medium.com/legacy-systems-diary/... bài viết tốt đẹp với các ví dụ về cách cẩn thận, bạn sẽ có trong khi sử dụng "INSERT bỏ qua" tuyên bố.
0x49D1

8

TRÊN CẬP NHẬT KHÓA NGHIÊM TRỌNG không thực sự trong tiêu chuẩn. Đó là về tiêu chuẩn như REPLACE. Xem SQL MERGE .

Về cơ bản cả hai lệnh đều là phiên bản cú pháp thay thế của các lệnh tiêu chuẩn.


1
thay thế xóa và chèn, trong khi cập nhật khóa trùng lặp cập nhật hàng hiện có. Một số khác biệt là: id tăng tự động, vị trí hàng, một loạt các kích hoạt
ahnbizcad

8

ReplaceVào có vẻ như là một lựa chọn. Hoặc bạn có thể kiểm tra với

IF NOT EXISTS(QUERY) Then INSERT

Điều này sẽ chèn hoặc xóa sau đó chèn. Tôi có xu hướng đi IF NOT EXISTSkiểm tra đầu tiên.


Cảm ơn đã trả lời nhanh chóng. Tôi đang giả định ở khắp mọi nơi, nhưng tôi cho rằng điều này sẽ tương tự như CẬP NHẬT KHÓA NGHIÊM TRỌNG ở chỗ nó sẽ thực hiện cập nhật không cần thiết. Nó có vẻ lãng phí, nhưng tôi không chắc chắn. Bất kỳ trong số này nên làm việc. Tôi tự hỏi nếu có ai biết cái nào là tốt nhất.
Thomas G Henry

6
NTuplip - giải pháp đó vẫn mở cho các điều kiện chạy đua từ các phần chèn bằng các giao dịch đồng thời.
Chris KL

REPLACE xóa tất cả các hàng trong bảng bằng khớp bất kỳ PRIMARY hoặc UNIQUEchìa khóa, sau đó INSERTs . Điều này có khả năng nhiều công việc hơn IODKU.
Rick James

4

Nguy cơ tiềm ẩn của INSERT IGNORE. Nếu bạn đang cố gắng chèn giá trị VARCHAR lâu hơn thì cột được xác định bằng - giá trị sẽ bị cắt bớt và chèn NGAY NẾU chế độ nghiêm ngặt được bật.


3

Nếu sử dụng insert ignore có một SHOW WARNINGS;câu lệnh ở cuối bộ truy vấn của bạn sẽ hiển thị một bảng có tất cả các cảnh báo, bao gồm cả ID nào là trùng lặp.


SHOW WARNINGS;dường như chỉ ảnh hưởng đến truy vấn mới nhất. Bất kỳ câu lệnh nào trước đây không được tích lũy, nếu bạn có nhiều hơn một câu lệnh.
Kawu

2

Nếu bạn muốn chèn vào bảng và xung đột của khóa chính hoặc chỉ mục duy nhất, nó sẽ cập nhật hàng xung đột thay vì chèn hàng đó.

Cú pháp:

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

Bây giờ ở đây, câu lệnh chèn này có thể trông khác với những gì bạn đã thấy trước đó. Câu lệnh chèn này cố gắng chèn một hàng trong bảng1 với giá trị của a và b vào cột cột 1 và cột2 tương ứng.

Hãy hiểu sâu sắc về tuyên bố này:

Ví dụ: ở đây cột1 được định nghĩa là khóa chính trong bảng1.

Bây giờ, nếu trong bảng1, không có hàng nào có giá trị LỚN trong cột1. Vì vậy, tuyên bố này sẽ chèn một hàng trong bảng1.

Bây giờ nếu trong bảng1 có một hàng có giá trị là một giá trị trong cột2. Vì vậy, tuyên bố này sẽ cập nhật giá trị cột2 của hàng với giá trị trong đó giá trị cột1 là một giá trị.

Vì vậy, nếu bạn muốn chèn một hàng mới, hãy cập nhật hàng đó vào xung đột của khóa chính hoặc chỉ mục duy nhất.
Đọc thêm về liên kết này


0

INSERT...ON DUPLICATE KEY UPDATE được ưa thích để ngăn chặn quản lý ngoại lệ không mong muốn.

Giải pháp này hoạt động khi bạn có ** 1 ràng buộc duy nhất **

Trong trường hợp của tôi, tôi biết điều đó col1col2tạo ra một chỉ số tổng hợp duy nhất.

Nó theo dõi lỗi, nhưng không đưa ra một ngoại lệ trên bản sao. Về hiệu suất, bản cập nhật có cùng giá trị là hiệu quả khi MySQL thông báo điều này và không cập nhật nó

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

Ý tưởng sử dụng phương pháp này xuất phát từ các ý kiến ​​tại phpdelusions.net/pdo .

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.