Các câu trả lời chấp nhận hiện nay có vẻ ok cho một mục tiêu duy nhất xung đột, ít xung đột, tuples nhỏ và không gây nên. Nó tránh được vấn đề đồng thời 1 (xem bên dưới) với lực lượng vũ phu. Giải pháp đơn giản có sức hấp dẫn của nó, các tác dụng phụ có thể ít quan trọng hơn.
Tuy nhiên, đối với tất cả các trường hợp khác, không cập nhật các hàng giống hệt nhau mà không cần. Ngay cả khi bạn thấy không có sự khác biệt trên bề mặt, có nhiều tác dụng phụ khác nhau :
Nó có thể kích hoạt kích hoạt không nên bắn.
Nó ghi các hàng "vô tội", có thể phát sinh chi phí cho các giao dịch đồng thời.
Nó có thể làm cho hàng có vẻ mới, mặc dù nó đã cũ (dấu thời gian giao dịch).
Quan trọng nhất , với mô hình MVCC của PostgreQuery, một phiên bản hàng mới được viết cho mọi người UPDATE
, bất kể dữ liệu hàng có thay đổi hay không. Điều này phải chịu một hình phạt hiệu suất cho chính UPSERT, phình bảng, phình chỉ mục, phạt hiệu năng cho các hoạt động tiếp theo trên bảng, VACUUM
chi phí. Một hiệu ứng nhỏ cho một vài bản sao, nhưng lớn cho hầu hết các bản sao .
Thêm vào đó , đôi khi nó không thực tế hoặc thậm chí có thể sử dụng ON CONFLICT DO UPDATE
. Hướng dẫn sử dụng:
Đối với ON CONFLICT DO UPDATE
, conflict_target
phải được cung cấp.
Không thể có một "mục tiêu xung đột" nếu có nhiều chỉ mục / ràng buộc.
Bạn có thể đạt được (gần như) như nhau mà không cần cập nhật trống và tác dụng phụ. Một số giải pháp sau đây cũng hoạt động với ON CONFLICT DO NOTHING
(không có "mục tiêu xung đột"), để nắm bắt tất cả các xung đột có thể xảy ra - có thể hoặc không thể mong muốn.
Không có tải ghi đồng thời
WITH input_rows(usr, contact, name) AS (
VALUES
(text 'foo1', text 'bar1', text 'bob1') -- type casts in first row
, ('foo2', 'bar2', 'bob2')
-- more?
)
, ins AS (
INSERT INTO chats (usr, contact, name)
SELECT * FROM input_rows
ON CONFLICT (usr, contact) DO NOTHING
RETURNING id --, usr, contact -- return more columns?
)
SELECT 'i' AS source -- 'i' for 'inserted'
, id --, usr, contact -- return more columns?
FROM ins
UNION ALL
SELECT 's' AS source -- 's' for 'selected'
, c.id --, usr, contact -- return more columns?
FROM input_rows
JOIN chats c USING (usr, contact); -- columns of unique index
Các source
cột là một sự bổ sung bắt buộc để chứng minh cách làm việc này. Bạn thực sự có thể cần nó để nói sự khác biệt giữa cả hai trường hợp (một lợi thế khác so với ghi trống).
Công JOIN chats
việc cuối cùng vì các hàng mới được chèn từ CTE sửa đổi dữ liệu đính kèm chưa được hiển thị trong bảng bên dưới. (Tất cả các phần của cùng một câu lệnh SQL đều nhìn thấy các ảnh chụp nhanh giống nhau của các bảng bên dưới.)
Vì VALUES
biểu thức là tự do (không được gắn trực tiếp vào một INSERT
) Postgres không thể lấy được các kiểu dữ liệu từ các cột mục tiêu và bạn có thể phải thêm các kiểu phôi rõ ràng. Hướng dẫn sử dụng:
Khi VALUES
được sử dụng INSERT
, tất cả các giá trị sẽ tự động được ép theo kiểu dữ liệu của cột đích tương ứng. Khi nó được sử dụng trong các bối cảnh khác, có thể cần phải chỉ định loại dữ liệu chính xác. Nếu các mục nhập là tất cả các hằng số được trích dẫn, việc ép buộc đầu tiên là đủ để xác định loại giả định cho tất cả.
Bản thân truy vấn (không tính các tác dụng phụ) có thể tốn kém hơn một chút cho một số bản sao, do chi phí hoạt động của CTE và phần bổ sung SELECT
(nên rẻ vì chỉ số hoàn hảo có theo định nghĩa - một ràng buộc duy nhất được thực hiện với một chỉ số).
Có thể (nhiều) nhanh hơn cho nhiều bản sao. Chi phí hiệu quả của việc viết thêm phụ thuộc vào nhiều yếu tố.
Nhưng có ít tác dụng phụ và chi phí ẩn trong mọi trường hợp. Nhìn chung, nó có lẽ rẻ hơn.
Trình tự đính kèm vẫn được nâng cao, vì các giá trị mặc định được điền vào trước khi kiểm tra xung đột.
Về CTE:
Với tải ghi đồng thời
Giả sử READ COMMITTED
cách ly giao dịch mặc định . Liên quan:
Chiến lược tốt nhất để bảo vệ chống lại các điều kiện chủng tộc phụ thuộc vào yêu cầu chính xác, số lượng và kích thước của các hàng trong bảng và trong các UPSERT, số lượng giao dịch đồng thời, khả năng xảy ra xung đột, tài nguyên có sẵn và các yếu tố khác ...
Vấn đề đồng thời 1
Nếu một giao dịch đồng thời được ghi vào một hàng mà giao dịch của bạn hiện đang cố gắng gửi tới UPSERT, giao dịch của bạn phải đợi một giao dịch khác kết thúc.
Nếu giao dịch khác kết thúc bằng ROLLBACK
(hoặc bất kỳ lỗi nào, tức là tự động ROLLBACK
), giao dịch của bạn có thể tiến hành bình thường. Tác dụng phụ nhỏ có thể xảy ra: khoảng trống trong các số liên tiếp. Nhưng không thiếu hàng.
Nếu giao dịch khác kết thúc bình thường (ẩn hoặc rõ ràng COMMIT
), bạn INSERT
sẽ phát hiện xung đột ( UNIQUE
chỉ số / ràng buộc là tuyệt đối) và DO NOTHING
do đó cũng không trả về hàng. (Cũng không thể khóa hàng như thể hiện trong đồng thời vấn đề 2 bên dưới, vì nó không nhìn thấy được .) Các SELECT
nhìn thấy ảnh chụp tương tự từ khi bắt đầu truy vấn và cũng không thể trả lại hàng nhưng vô hình.
Bất kỳ hàng nào như vậy bị thiếu trong tập kết quả (mặc dù chúng tồn tại trong bảng bên dưới)!
Điều này có thể là ok như là . Đặc biệt nếu bạn không trả về các hàng như trong ví dụ và hài lòng khi biết hàng đó ở đó. Nếu điều đó không đủ tốt, có nhiều cách khác nhau xung quanh nó.
Bạn có thể kiểm tra số hàng của đầu ra và lặp lại câu lệnh nếu nó không khớp với số hàng của đầu vào. Có thể đủ tốt cho trường hợp hiếm. Vấn đề là bắt đầu một truy vấn mới (có thể trong cùng một giao dịch), sau đó sẽ thấy các hàng mới được cam kết.
Hoặc kiểm tra các hàng kết quả bị thiếu trong cùng một truy vấn và ghi đè lên các hàng bằng thủ thuật vũ phu được thể hiện trong câu trả lời của Alexton .
WITH input_rows(usr, contact, name) AS ( ... ) -- see above
, ins AS (
INSERT INTO chats AS c (usr, contact, name)
SELECT * FROM input_rows
ON CONFLICT (usr, contact) DO NOTHING
RETURNING id, usr, contact -- we need unique columns for later join
)
, sel AS (
SELECT 'i'::"char" AS source -- 'i' for 'inserted'
, id, usr, contact
FROM ins
UNION ALL
SELECT 's'::"char" AS source -- 's' for 'selected'
, c.id, usr, contact
FROM input_rows
JOIN chats c USING (usr, contact)
)
, ups AS ( -- RARE corner case
INSERT INTO chats AS c (usr, contact, name) -- another UPSERT, not just UPDATE
SELECT i.*
FROM input_rows i
LEFT JOIN sel s USING (usr, contact) -- columns of unique index
WHERE s.usr IS NULL -- missing!
ON CONFLICT (usr, contact) DO UPDATE -- we've asked nicely the 1st time ...
SET name = c.name -- ... this time we overwrite with old value
-- SET name = EXCLUDED.name -- alternatively overwrite with *new* value
RETURNING 'u'::"char" AS source -- 'u' for updated
, id --, usr, contact -- return more columns?
)
SELECT source, id FROM sel
UNION ALL
TABLE ups;
Giống như truy vấn ở trên, nhưng chúng tôi thêm một bước nữa với CTE ups
, trước khi chúng tôi trả về tập kết quả hoàn chỉnh . Đó là CTE cuối cùng sẽ không làm gì hầu hết thời gian. Chỉ khi hàng bị mất từ kết quả trả về, chúng tôi sử dụng lực lượng vũ phu.
Nhiều chi phí hơn, chưa. Càng nhiều xung đột với các hàng có sẵn, điều này càng có khả năng tốt hơn phương pháp đơn giản.
Một tác dụng phụ: UPSERT thứ 2 ghi các hàng không theo thứ tự, do đó, nó sẽ giới thiệu lại khả năng của các khóa chết (xem bên dưới) nếu ba hoặc nhiều giao dịch ghi vào cùng một hàng trùng nhau. Nếu đó là một vấn đề, bạn cần một giải pháp khác - như lặp lại toàn bộ tuyên bố như đã đề cập ở trên.
Vấn đề đồng thời 2
Nếu các giao dịch đồng thời có thể ghi vào các cột liên quan của các hàng bị ảnh hưởng và bạn phải đảm bảo các hàng bạn tìm thấy vẫn còn ở giai đoạn sau trong cùng một giao dịch, bạn có thể khóa các hàng hiện có với giá rẻ trong CTE ins
(nếu không sẽ được mở khóa) với:
...
ON CONFLICT (usr, contact) DO UPDATE
SET name = name WHERE FALSE -- never executed, but still locks the row
...
Và thêm một điều khoản khóa SELECT
là tốt, nhưFOR UPDATE
.
Điều này làm cho các hoạt động ghi cạnh tranh chờ đến khi kết thúc giao dịch, khi tất cả các khóa được phát hành. Vì vậy, hãy ngắn gọn.
Thêm chi tiết và giải thích:
Bế tắc?
Bảo vệ chống lại bế tắc bằng cách chèn các hàng theo thứ tự nhất quán . Xem:
Kiểu dữ liệu và phôi
Bảng hiện tại làm mẫu cho các loại dữ liệu ...
Loại phôi rõ ràng cho hàng dữ liệu đầu tiên trong VALUES
biểu thức đứng tự do có thể bất tiện. Có nhiều cách xung quanh nó. Bạn có thể sử dụng bất kỳ mối quan hệ hiện có (bảng, dạng xem, ...) làm mẫu hàng. Bảng đích là sự lựa chọn rõ ràng cho trường hợp sử dụng. Dữ liệu đầu vào được tự động ép buộc thành các loại thích hợp, như trong VALUES
mệnh đề của một INSERT
:
WITH input_rows AS (
(SELECT usr, contact, name FROM chats LIMIT 0) -- only copies column names and types
UNION ALL
VALUES
('foo1', 'bar1', 'bob1') -- no type casts here
, ('foo2', 'bar2', 'bob2')
)
...
Điều này không làm việc cho một số loại dữ liệu. Xem:
... và tên
Điều này cũng hoạt động cho tất cả các loại dữ liệu.
Trong khi chèn vào tất cả các cột (hàng đầu) của bảng, bạn có thể bỏ qua các tên cột. Bảng giả sử chats
trong ví dụ chỉ bao gồm 3 cột được sử dụng trong UPSERT:
WITH input_rows AS (
SELECT * FROM (
VALUES
((NULL::chats).*) -- copies whole row definition
('foo1', 'bar1', 'bob1') -- no type casts needed
, ('foo2', 'bar2', 'bob2')
) sub
OFFSET 1
)
...
Bên cạnh: không sử dụng từ dành riêng như "user"
là định danh. Đó là một khẩu súng ngắn nạp đạn. Sử dụng định danh hợp pháp, chữ thường, không trích dẫn. Tôi đã thay thế nó bằng usr
.
ON CONFLICT UPDATE
để có một sự thay đổi cho hàng. Sau đóRETURNING
sẽ chụp nó.