9.5 dan yang lebih baru:
PostgreSQL 9.5 dan yang lebih baru mendukung INSERT ... ON CONFLICT (key) DO UPDATE
(dan ON CONFLICT (key) DO NOTHING
), yaitu upsert.
Perbandingan dengan ON DUPLICATE KEY UPDATE
.
Penjelasan cepat.
Untuk penggunaan lihat manual - khususnya conflict_action klausa dalam diagram sintaks, dan teks penjelasan.
Tidak seperti solusi untuk 9.4 dan yang lebih lama yang diberikan di bawah ini, fitur ini bekerja dengan beberapa baris yang bertentangan dan tidak memerlukan penguncian eksklusif atau pengulangan percobaan.
Komitmen untuk menambahkan fitur ada di sini dan diskusi seputar pengembangannya ada di sini.
Jika Anda menggunakan 9.5 dan tidak perlu kompatibel dengan versi sebelumnya, Anda dapat berhenti membaca sekarang .
9.4 dan yang lebih lama:
PostgreSQL tidak memiliki UPSERT
bawaan (atau MERGE
) fasilitas, dan melakukannya secara efisien dalam menghadapi penggunaan bersamaan sangat sulit.
Artikel ini membahas masalah dengan detail yang berguna.
Secara umum Anda harus memilih di antara dua opsi:
- Operasi penyisipan/pembaruan individu dalam pengulangan percobaan; atau
- Mengunci tabel dan melakukan penggabungan batch
Loop percobaan ulang baris individu
Menggunakan upsert baris individual dalam loop percobaan ulang adalah opsi yang masuk akal jika Anda ingin banyak koneksi secara bersamaan mencoba melakukan penyisipan.
Dokumentasi PostgreSQL berisi prosedur berguna yang memungkinkan Anda melakukan ini dalam satu lingkaran di dalam database. Ini menjaga dari pembaruan yang hilang dan memasukkan balapan, tidak seperti kebanyakan solusi naif. Ini hanya akan berfungsi di READ COMMITTED
mode dan hanya aman jika itu satu-satunya hal yang Anda lakukan dalam transaksi. Fungsi tidak akan berfungsi dengan benar jika pemicu atau kunci unik sekunder menyebabkan pelanggaran unik.
Strategi ini sangat tidak efisien. Kapan pun praktis, Anda harus mengantre pekerjaan dan melakukan upser massal seperti yang dijelaskan di bawah ini.
Banyak solusi yang dicoba untuk masalah ini gagal mempertimbangkan rollback, sehingga menghasilkan pembaruan yang tidak lengkap. Dua transaksi berpacu satu sama lain; salah satunya berhasil INSERT
s; yang lain mendapatkan kesalahan kunci duplikat dan melakukan UPDATE
sebagai gantinya. UPDATE
blok menunggu INSERT
untuk mengembalikan atau melakukan. Saat diputar kembali, UPDATE
pemeriksaan ulang kondisi cocok dengan nol baris, jadi meskipun UPDATE
melakukan itu belum benar-benar melakukan upsert yang Anda harapkan. Anda harus memeriksa jumlah baris hasil dan mencoba lagi jika perlu.
Beberapa solusi yang dicoba juga gagal mempertimbangkan balapan SELECT. Jika Anda mencoba yang jelas dan sederhana:
-- THIS IS WRONG. DO NOT COPY IT. It's an EXAMPLE.
BEGIN;
UPDATE testtable
SET somedata = 'blah'
WHERE id = 2;
-- Remember, this is WRONG. Do NOT COPY IT.
INSERT INTO testtable (id, somedata)
SELECT 2, 'blah'
WHERE NOT EXISTS (SELECT 1 FROM testtable WHERE testtable.id = 2);
COMMIT;
kemudian ketika dua dijalankan sekaligus ada beberapa mode kegagalan. Salah satunya adalah masalah yang sudah dibahas dengan pemeriksaan ulang pembaruan. Lain adalah di mana keduanya UPDATE
pada saat yang sama, mencocokkan baris nol dan melanjutkan. Kemudian mereka berdua melakukan EXISTS
tes, yang terjadi sebelum INSERT
. Keduanya mendapatkan baris nol, jadi keduanya melakukan INSERT
. Satu gagal dengan kesalahan kunci duplikat.
Inilah sebabnya mengapa Anda memerlukan pengulangan percobaan. Anda mungkin berpikir bahwa Anda dapat mencegah kesalahan kunci duplikat atau kehilangan pembaruan dengan SQL pintar, tetapi Anda tidak bisa. Anda perlu memeriksa jumlah baris atau menangani kesalahan kunci duplikat (bergantung pada pendekatan yang dipilih) dan mencoba kembali.
Tolong jangan menggulung solusi Anda sendiri untuk ini. Seperti dengan antrian pesan, itu mungkin salah.
Upser massal dengan kunci
Terkadang Anda ingin melakukan upsert massal, di mana Anda memiliki kumpulan data baru yang ingin Anda gabungkan ke dalam kumpulan data lama yang sudah ada. Ini sangat lebih efisien daripada upser baris individual dan harus dipilih kapan pun praktis.
Dalam hal ini, Anda biasanya mengikuti proses berikut:
-
CREATE
sebuahTEMPORARY
tabel -
COPY
atau masukkan data baru secara massal ke tabel temp -
LOCK
tabel targetIN EXCLUSIVE MODE
. Ini mengizinkan transaksi lain untukSELECT
, tetapi tidak membuat perubahan apa pun pada tabel. -
Lakukan
UPDATE ... FROM
catatan yang ada menggunakan nilai dalam tabel temp; -
Lakukan
INSERT
baris yang belum ada di tabel target; -
COMMIT
, melepaskan kunci.
Misalnya, untuk contoh yang diberikan dalam pertanyaan, menggunakan multi-nilai INSERT
untuk mengisi tabel temp:
BEGIN;
CREATE TEMPORARY TABLE newvals(id integer, somedata text);
INSERT INTO newvals(id, somedata) VALUES (2, 'Joe'), (3, 'Alan');
LOCK TABLE testtable IN EXCLUSIVE MODE;
UPDATE testtable
SET somedata = newvals.somedata
FROM newvals
WHERE newvals.id = testtable.id;
INSERT INTO testtable
SELECT newvals.id, newvals.somedata
FROM newvals
LEFT OUTER JOIN testtable ON (testtable.id = newvals.id)
WHERE testtable.id IS NULL;
COMMIT;
Bacaan terkait
- UPSERT halaman wiki
- UPSERTisme di Postgres
- Sisipkan, pada pembaruan duplikat di PostgreSQL?
- http://petereisentraut.blogspot.com/2010/05/merge-syntax.html
- Upsert dengan transaksi
- Apakah SELECT atau INSERT dalam fungsi rentan terhadap kondisi balapan?
- SQL
MERGE
di wiki PostgreSQL - Cara paling idiomatis untuk mengimplementasikan UPSERT di Postgresql saat ini
Bagaimana dengan MERGE
?
MERGE
standar SQL sebenarnya memiliki semantik konkurensi yang tidak terdefinisi dengan baik dan tidak cocok untuk upserting tanpa mengunci tabel terlebih dahulu.
Ini adalah pernyataan OLAP yang sangat berguna untuk penggabungan data, tetapi sebenarnya bukan solusi yang berguna untuk upsert yang aman secara konkurensi. Ada banyak saran untuk orang yang menggunakan DBMS lain untuk menggunakan MERGE
untuk upser, tapi sebenarnya salah.
DB lain:
INSERT ... ON DUPLICATE KEY UPDATE
di MySQLMERGE
dari MS SQL Server (tapi lihat di atas tentangMERGE
masalah)MERGE
dari Oracle (tapi lihat di atas tentangMERGE
masalah)