Şu anda kabul edilen yanıt , tek bir çatışma hedefi, az sayıda çatışma, küçük demetler ve tetikleyici yok için uygun görünüyor. Kaba kuvvet ile eşzamanlılık sorunu 1'i (aşağıya bakın) önler . Basit çözümün çekiciliği vardır, yan etkiler daha az önemli olabilir.
Diğer tüm durumlar için olsa da, do not gerek kalmadan aynı satırları güncelleştirmek. Yüzeyde bir fark görmeseniz bile çeşitli yan etkileri vardır :
Ateşlenmemesi gereken tetikleyicileri ateşleyebilir.
Muhtemelen eşzamanlı işlemler için maliyete neden olan "masum" satırları yazıp kilitler.
Eski olmasına rağmen satırın yeni görünmesine neden olabilir (işlem zaman damgası).
En önemlisi , PostgreSQL'in MVCC modelindeUPDATE
, satır verilerinin değişip değişmediğine bakılmaksızın her biri için yeni bir satır sürümü yazılır . Bu, UPSERT'nin kendisi için bir performans cezasına, masa şişmesine, indeks şişmesine, masadaki sonraki işlemler için performans kaybına, VACUUM
maliyete neden olur. Birkaç kopya için küçük bir etki, ancak çoğunlukla kopyalar için büyük bir etki .
Artı , bazen pratik değildir ve hatta kullanılması mümkün değildir ON CONFLICT DO UPDATE
. Kullanım kılavuzu:
İçin ON CONFLICT DO UPDATE
bir conflict_target
sağlanmalıdır.
Bir tek çoklu endeksler / kısıtlamalar dahil değilseniz "çatışma hedef" mümkün değildir.
Boş güncellemeler ve yan etkiler olmadan (neredeyse) aynı şeyi elde edebilirsiniz. Aşağıdaki çözümlerden bazıları, ortaya çıkabilecek olası tüm çatışmaları ON CONFLICT DO NOTHING
yakalamak için ("çatışma hedefi" yok) ile de çalışır - ki bu arzu edilebilir veya olmayabilir.
Eşzamanlı yazma yükü olmadan
WITH input_rows(usr, contact, name) AS (
VALUES
(text 'foo1', text 'bar1', text 'bob1')
, ('foo2', 'bar2', 'bob2')
)
, ins AS (
INSERT INTO chats (usr, contact, name)
SELECT * FROM input_rows
ON CONFLICT (usr, contact) DO NOTHING
RETURNING id
)
SELECT 'i' AS source
, id
FROM ins
UNION ALL
SELECT 's' AS source
, c.id
FROM input_rows
JOIN chats c USING (usr, contact);
source
Sütun Bunun nasıl çalıştığını göstermek için isteğe bağlı ektir. Aslında her iki durum arasındaki farkı söylemek için buna ihtiyacınız olabilir (boş yazmalara göre başka bir avantaj).
Son JOIN chats
çalışma, eklenmiş bir veri değiştirici CTE'den yeni eklenen satırlar temel tabloda henüz görünmediği için çalışır. (Aynı SQL ifadesinin tüm bölümleri, temel tabloların aynı anlık görüntülerini görür.)
Yana VALUES
ifadesi (doğrudan bağlı olmayan serbest duran olduğu INSERT
) Postgres hedef sütunları ve varsa türet veri tipleri açık tür yayınları eklemek mümkün değil. Kullanım kılavuzu:
İçinde VALUES
kullanıldığında INSERT
, değerlerin tümü otomatik olarak karşılık gelen hedef sütununun veri türüne zorlanır. Diğer bağlamlarda kullanıldığında, doğru veri türünü belirtmek gerekebilir. Girişlerin tümü alıntılanmış değişmez sabitlerse, ilkinin zorlanması, tümü için varsayılan türü belirlemek için yeterlidir.
Sorgunun kendisi (yan etkileri saymaz), CTE'nin ek yükü ve ek (mükemmel indeks tanım gereği olduğu için ucuz olmalıdır - benzersiz bir kısıtlama uygulanır) nedeniyle birkaç kopya için biraz daha pahalı olabilir. SELECT
bir dizin).
Birçok kopya için (çok) daha hızlı olabilir . Ek yazma işlemlerinin etkin maliyeti birçok faktöre bağlıdır.
Ancak her durumda daha az yan etki ve gizli maliyet vardır. Muhtemelen genel olarak daha ucuzdur.
Çakışmaları test etmeden önce varsayılan değerler doldurulduğundan, ekli diziler hala ileri düzeydedir .
CTE'ler hakkında:
Eşzamanlı yazma yükü ile
Varsayılan READ COMMITTED
işlem izolasyonu varsayılır . İlişkili:
Yarış koşullarına karşı en iyi savunma stratejisi, kesin gerekliliklere, tablodaki ve UPSERT'lerdeki satırların sayısına ve boyutuna, eşzamanlı işlemlerin sayısına, çatışma olasılığına, mevcut kaynaklara ve diğer faktörlere bağlıdır ...
Eşzamanlılık sorunu 1
Eşzamanlı bir işlem, işleminizin şimdi UPSERT'ye çalıştığı bir satıra yazıldıysa, işleminizin diğerinin bitmesini beklemesi gerekir.
Diğer işlem ROLLBACK
(veya herhangi bir hata, yani otomatik ROLLBACK
) ile biterse , işleminiz normal şekilde devam edebilir. Küçük olası yan etkiler: ardışık sayılardaki boşluklar. Ancak eksik satır yok.
Diğer işlem normal şekilde biterse (örtük veya açık COMMIT
), INSERT
bir çakışma algılarsınız ( UNIQUE
dizin / kısıtlama mutlaktır) ve DO NOTHING
dolayısıyla satırı da döndürmezsiniz. (Ayrıca, görünmediği için aşağıdaki eşzamanlılık sorunu 2'de gösterildiği gibi satırı kilitleyemez .) Sorgunun başından itibaren aynı anlık görüntüyü görür ve henüz görünmeyen satırı döndüremez.SELECT
Sonuç kümesinde bu tür satırlar eksiktir (alttaki tabloda bulunsalar bile)!
Bu gibi Tamam olabilir . Özellikle örnekteki gibi satırları döndürmüyorsanız ve satırın orada olduğunu bilmekten memnunsanız. Bu yeterince iyi değilse, etrafında çeşitli yollar vardır.
Çıktının satır sayısını kontrol edebilir ve girdinin satır sayısıyla eşleşmiyorsa ifadeyi tekrarlayabilirsiniz. Nadir durum için yeterince iyi olabilir. Önemli olan yeni bir sorgu başlatmaktır (aynı işlemde olabilir), bu daha sonra yeni taahhüt edilen satırları görür.
Veya aynı sorgu içinde eksik sonuç satırlarını kontrol edin ve Alextoni'nin cevabında gösterilen kaba kuvvet numarasıyla bunların üzerine yazın .
WITH input_rows(usr, contact, name) AS ( ... )
, ins AS (
INSERT INTO chats AS c (usr, contact, name)
SELECT * FROM input_rows
ON CONFLICT (usr, contact) DO NOTHING
RETURNING id, usr, contact
)
, sel AS (
SELECT 'i'::"char" AS source
, id, usr, contact
FROM ins
UNION ALL
SELECT 's'::"char" AS source
, c.id, usr, contact
FROM input_rows
JOIN chats c USING (usr, contact)
)
, ups AS (
INSERT INTO chats AS c (usr, contact, name)
SELECT i.*
FROM input_rows i
LEFT JOIN sel s USING (usr, contact)
WHERE s.usr IS NULL
ON CONFLICT (usr, contact) DO UPDATE
SET name = c.name
RETURNING 'u'::"char" AS source
, id
)
SELECT source, id FROM sel
UNION ALL
TABLE ups;
Yukarıdaki sorgu gibi, ancak sonuç kümesinin tamamınıups
döndürmeden önce CTE ile bir adım daha ekliyoruz . Bu son CTE çoğu zaman hiçbir şey yapmaz. Sadece dönen sonuçtan satırlar eksik kalırsa, kaba kuvvet kullanırız.
Henüz daha fazla ek yük. Önceden var olan satırlarla ne kadar fazla çakışma olursa, bu basit yaklaşımdan o kadar iyi performans gösterecektir.
Bir yan etki: 2. UPSERT, satırları sırasız yazar, bu nedenle aynı satırlara yazılan üç veya daha fazla işlemin üst üste gelmesi durumunda kilitlenme olasılığını yeniden sunar (aşağıya bakın) . Bu bir sorunsa, yukarıda belirtildiği gibi tüm ifadeyi tekrarlamak gibi farklı bir çözüme ihtiyacınız var.
Eşzamanlılık sorunu 2
Eşzamanlı işlemler, etkilenen satırların ilgili sütunlarına yazabiliyorsa ve bulduğunuz satırların aynı işlemde daha sonraki bir aşamada hala orada olduğundan emin olmanız gerekiyorsa, CTE'deki mevcut satırları ucuza kilitleyebilirsinizins
(aksi takdirde kilidi açılacaktır) ile:
...
ON CONFLICT (usr, contact) DO UPDATE
SET name = name WHERE FALSE
...
Ve , gibi bir kilitleme cümlesiSELECT
FOR UPDATE
ekleyin .
Bu, rakip yazma işlemlerinin, tüm kilitler serbest bırakıldığında işlemin sonuna kadar beklemesini sağlar. Bu yüzden kısa olun.
Daha fazla ayrıntı ve açıklama:
Kilitlenme mi?
Satırları tutarlı bir sırayla ekleyerek kilitlenmelere karşı savunun . Görmek:
Veri türleri ve yayınlar
Veri türleri için şablon olarak mevcut tablo ...
Serbest duran VALUES
ifadedeki ilk veri satırı için açık tip yayınlar uygun olmayabilir. Etrafında yollar var. Mevcut herhangi bir ilişkiyi (tablo, görünüm, ...) satır şablonu olarak kullanabilirsiniz. Hedef tablo, kullanım durumu için bariz bir seçimdir. Girdi verileri gibi, otomatik olarak uygun türlerine zorlama bir VALUES
bir bölgesinin maddesi INSERT
:
WITH input_rows AS (
(SELECT usr, contact, name FROM chats LIMIT 0)
UNION ALL
VALUES
('foo1', 'bar1', 'bob1')
, ('foo2', 'bar2', 'bob2')
)
...
Bu, bazı veri türleri için çalışmaz. Görmek:
... ve isimler
Bu aynı zamanda tüm veri türleri için de geçerlidir .
Tablonun tüm (önde gelen) sütunlarına eklerken, sütun adlarını atlayabilirsiniz. chats
Örnekteki tablonun yalnızca UPSERT'de kullanılan 3 sütundan oluştuğunu varsayarsak :
WITH input_rows AS (
SELECT * FROM (
VALUES
((NULL::chats).*)
('foo1', 'bar1', 'bob1')
, ('foo2', 'bar2', 'bob2')
) sub
OFFSET 1
)
...
Bir kenara: tanımlayıcı gibi ayrılmış kelimeler kullanmayın "user"
. Bu dolu bir tabanca. Yasal, küçük harfli, alıntılanmamış tanımlayıcılar kullanın. İle değiştirdim usr
.
ON CONFLICT UPDATE
bir değişiklik olması için kullanın . SonraRETURNING
onu yakalayacak.