Atomik işlemlerde benzersiz ihlalden kaçının


15

PostgreSQL'de atomik işlem oluşturmak mümkün müdür?

Bu satırlarla tablo kategorisine sahip olduğumu düşünün:

id|name
--|---------
1 |'tablets'
2 |'phones'

Ve sütun adı benzersiz bir kısıtlamaya sahiptir.

Eğer denersem:

BEGIN;
update "category" set name = 'phones' where id = 1;
update "category" set name = 'tablets' where id = 2;
COMMIT;

Ben alıyorum:

ERROR:  duplicate key value violates unique constraint "category_name_key"
DETAIL:  Key (name)=(tablets) already exists.

Yanıtlar:


24

@Craig'in sağladığı şeye ek olarak (ve bazılarını düzeltir):

Etkili Postgres 9.4 , UNIQUE, PRIMARY KEYve EXCLUDEkısıtlamalar hemen kontrol edilir , her satır sonra tanımlandığı zaman NOT DEFERRABLE. Bu, her ifadeden sonra kontrol edilen diğer NOT DEFERRABLEkısıtlama türlerinden (şu anda sadece REFERENCES(yabancı anahtar)) farklıdır . Tüm bunları SO ile ilgili bu soru altında çalıştık:

Öyle değil bir yeterli UNIQUE(veya PRIMARY KEYveya EXCLUDE) kısıt olarak DEFERRABLEbirlikte sunulan kodunu yapmak için birden fazla ifadeleri çalışması.

Ve yapabilirsiniz değil kullanmak ALTER TABLE ... ALTER CONSTRAINTbu amaçla. Belgelere göre:

ALTER CONSTRAINT

Bu form, önceden oluşturulmuş bir kısıtlamanın niteliklerini değiştirir. Şu anda yalnızca yabancı anahtar kısıtlamaları değiştirilebilir .

Cesur vurgu benim. Bunun yerine kullanın:

ALTER TABLE t
   DROP CONSTRAINT category_name_key
 , ADD  CONSTRAINT category_name_key UNIQUE(name) DEFERRABLE;

Sınırı tek bir ifadeye bırakın ve ekleyin, böylece kimsenin rahatsız edici satırlara gizlice girmesi için zaman penceresi kalmaz. Büyük tablolar için, temeldeki benzersiz dizini bir şekilde korumak cazip gelebilir, çünkü onu silmek ve yeniden oluşturmak pahalıya mal olur. Ne yazık ki, bu standart araçlarla mümkün görünmüyor (bunun için bir çözümünüz varsa, lütfen bize bildirin!):

Bir İçin Tek açıklamada kısıtlama ertelenebilir hale yeterlidir:

UPDATE category c
SET    name = c_old.name
FROM   category c_old
WHERE  c.id     IN (1,2)
AND    c_old.id IN (1,2)
AND    c.id <> c_old.id;

CTE'lerle yapılan bir sorgu da tek bir ifadedir:

WITH x AS (
    UPDATE category SET name = 'phones' WHERE id = 1
    )
UPDATE category SET name = 'tablets' WHERE id = 2;

Bununla birlikte , birden fazla ifadeye sahip kodunuz için (ek olarak) kısıtlamayı gerçekten ertelemeniz gerekir - ya da her INITIALLY DEFERREDikisinden biri genellikle yukarıdakilerden daha pahalıdır. Ancak her şeyi tek bir ifadeye toplamak kolay olmayabilir.

BEGIN;
SET CONSTRAINTS category_name_key DEFERRED;
UPDATE category SET name = 'phones'  WHERE id = 1;
UPDATE category SET name = 'tablets' WHERE id = 2;
COMMIT;

Bununla birlikte, kısıtlamalarla ilgili bir sınırlamanın farkında olun FOREIGN KEY. Belgelere göre:

Başvurulan sütunlar başvurulan tablodaki ertelenemez benzersiz veya birincil anahtar kısıtlamasının sütunları olmalıdır .

Yani ikisine de aynı anda sahip olamazsınız.


13

Anladığım kadarıyla, buradaki sorun, her ifadeden sonra kısıtlamanın kontrol edilmesidir, ancak işlemin sonunda kontrol edilmesini istersiniz, bu nedenle önceki durumu ara durumları yok sayarak önceki duruma göre karşılaştırır.

Eğer öyleyse, bu ertelenebilir bir kısıtlamayla mümkündür .

Belgelendirildiği gibi bakın SET CONSTRAINTSve DEFERRABLEkısıtlayın CREATE TABLE.

Ertelenmiş kısıtlamaların maliyetlerinin olduğuna dikkat edin - sistemin işlem sırasında kontrol etmek için bir listesini tutması gerekir, bu nedenle büyük değişiklikler yapan işlemler için iyi değildir. Ayrıca kontrol etmek daha yavaştır.

Bu yüzden muhtemelen şunları yapmak istiyorsunuz:

ALTER TABLE mytable ALTER CONSTRAINT category_name_key DEFERRABLE;

Kısıtlamaların ALTER TABLEayarlanmasında bir sınırlama olduğu görülmektedir DEFERRABLE; bunun yerine kısıtlamayı DROPyeniden yapmanız gerekebilir ADD.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.