PostgreSQL'de eşzamanlı DELETE / INSERT ile kilitleme sorunu


35

Bu oldukça basit, ama PG'nin yaptıklarından şaşkınlık duyuyorum (v9.0). Basit bir tabloyla başlıyoruz:

CREATE TABLE test (id INT PRIMARY KEY);

ve birkaç satır:

INSERT INTO TEST VALUES (1);
INSERT INTO TEST VALUES (2);

Favori JDBC sorgulama aracımı (ExecuteQuery) kullanarak, bu tablonun yaşadığı db'ye iki oturum penceresi bağladım. Her ikisi de işlemseldir (yani, auto-commit = false). Onlara S1 ve S2 diyelim.

Her biri için aynı kod biti:

1:DELETE FROM test WHERE id=1;
2:INSERT INTO test VALUES (1);
3:COMMIT;

Şimdi, bunu yavaşça çalıştırın, pencerelerde tek tek uygulayın.

S1-1 runs (1 row deleted)
S2-1 runs (but is blocked since S1 has a write lock)
S1-2 runs (1 row inserted)
S1-3 runs, releasing the write lock
S2-1 runs, now that it can get the lock. But reports 0 rows deleted. HUH???
S2-2 runs, reports a unique key constraint violation

Şimdi, bu SQLServer'da iyi çalışıyor. S2 sildiğinde, 1 satırın silindiğini bildirir. Ve sonra S2'nin ek parçası iyi çalışıyor.

PostgreSQL'in o satırın bulunduğu tabloda dizini kilitlediğinden şüpheleniyorum, oysa SQLServer gerçek anahtar değerini kilitliyor.

Haklı mıyım Bu işe yarayabilir mi?

Yanıtlar:


39

Mat ve Erwin her ikisi de haklıdır ve sadece bir yoruma uymayacak şekilde söylediklerini daha da genişletmek için başka bir cevap ekliyorum. Verdikleri cevaplar herkesi tatmin etmiyor gibi görünüyor ve PostgreSQL geliştiricilerine danışılması gereken bir öneri vardı ve ben bir tane olacağım.

Buradaki önemli nokta, SQL standardı altında, READ COMMITTEDişlem izolasyon düzeyinde çalışan bir işlem içinde , kısıtlamanın, tamamlanmamış işlemlerin çalışmasının görünmemesi gerektiğidir. Ne zaman işlenen işlemlerin çalışma görünür hale uygulama bağlıdır. Dikkatinizi çektiğiniz şey, iki ürünün bu uygulamayı nasıl seçtiğindeki fark. Her iki uygulama da standardın gereklerini ihlal etmemektedir.

İşte PostgreSQL'in içinde detaylı olarak olanlar:

S1-1 çalışır (1 satır silindi)

Eski satır yerinde kalır, çünkü S1 hala geri dönebilir, ancak S1 şimdi satır üzerinde bir kilit tutar, böylece satırı değiştirmeyi deneyen herhangi bir oturum S1'in tamamlanıp geri alınmayacağını görmek için bekler. Tablonun herhangi bir okunu , SELECT FOR UPDATEya da ile kilitlemeye çalışmadıkça eski satırı görebilir SELECT FOR SHARE.

S2-1 çalışıyor (ancak S1'in yazma kilidi olduğu için engellendi)

S2 şimdi S1'in sonucunu görmek için beklemek zorunda. S1 yerine taahhüt geri almak olsaydı, S2 satırı silerdi. S1, geri dönmeden önce yeni bir sürüm eklerse, yeni sürümün hiçbir zaman başka bir işlemin perspektifinde bulunmadığını, eski sürümün de başka bir işlemin perspektifinden silinmeyeceğini unutmayın.

S1-2 çalışır (1 satır eklendi)

Bu satır eskisinden bağımsızdır. Satır kimliği = 1 olan bir güncelleme olsaydı, eski ve yeni sürümler ilişkilendirilirdi ve S2, engellendiğinde satırın güncellenmiş sürümünü silebilirdi. Yeni bir satırın geçmişte varolan bazı satırlarla aynı değerlere sahip olması, o satırın güncellenmiş bir sürümüyle aynı olmasını sağlamaz.

S1-3 çalışır, yazma kilidini açar

Böylece S1'in değişiklikleri devam etti. Bir satır gitti. Bir satır eklendi.

S2-1 çalışır, şimdi kilidi alabilir. Ancak 0 satır silindi. BU NE ???

Dahili olarak gerçekleşen şey, bir satırın bir sürümünden, aynı satırın bir sonraki sürümüne güncellendiğinde bir işaretçi olduğudur. Satır silinirse, bir sonraki sürüm yoktur. Bir READ COMMITTEDişlem, bir yazma çatışması üzerindeki bir bloktan uyandığında, bu güncelleme zincirini sonuna kadar takip eder; Satır silinmediyse ve hala sorgunun seçim kriterlerine uyuyorsa işleme alınacaktır. Bu satır silindi, S2'nin sorgusu devam ediyor.

S2, tablo taraması sırasında yeni satıra geçebilir veya gelmeyebilir. Eğer öyleyse, S2'nin DELETEifadesi başladıktan sonra yeni satırın yaratıldığını görecek ve bu nedenle de görülebilir satır kümesinin bir parçası değildir.

PostgreSQL S2'nin tüm DELETE deyimini yeni bir anlık görüntü ile baştan başlatırsa, SQL Server ile aynı şekilde davranırdı. PostgreSQL topluluğu, performans nedenleriyle bunu yapmayı seçmedi. Bu basit durumda, performans farkını asla farketmezsiniz, ancak DELETEbloke olduğunuzda on milyon sıra olsaydınız, kesinlikle anlarsınız. Burada PostgreSQL'in performans seçtiği takas var, çünkü daha hızlı sürüm hala standardın şartlarına uyuyor.

S2-2 çalışır, benzersiz bir anahtar kısıtlama ihlali bildirir

Tabii ki, sıra zaten var. Bu, resmin en az şaşırtıcı kısmı.

Burada bazı şaşırtıcı davranışlar olsa da, her şey SQL standardına uygun ve standarda göre "uygulamaya özgü" sınırlar dahilinde. Diğer uygulamaların davranışlarının tüm uygulamalarda mevcut olacağını varsayıyorsanız, kesinlikle şaşırtıcı olabilir, ancak PostgreSQL, READ COMMITTEDyalıtım düzeyindeki serileştirme hatalarını önlemek için çok çaba sarf eder ve bunu başarmak için diğer ürünlerden farklı bazı davranışlara izin verir.

Şimdi, şahsen ben herhangi bir ürünün uygulamasında READ COMMITTEDişlem izolasyon seviyesinin büyük bir hayranı değilim . Hepsi yarış koşullarının işlemsel açıdan şaşırtıcı davranışlar yaratmasına izin veriyor. Birisi bir ürünün izin verdiği garip davranışlara alıştığında, “normal” ve başka bir ürünün seçtiği takasların tuhaf olduğunu düşünmeye meyillidirler. Ancak her ürün, aslında uygulanmayan herhangi bir mod için bir çeşit takas yapmak zorundadır . PostgreSQL geliştiricilerinin çizgiyi çizmeyi seçtikleri yerde engellemeyi en aza indirmek (okuma yazmalarını engellemiyor ve yazma okumaları engellemiyor) ve seri hale getirme başarısızlıklarını en aza indirmektir.SERIALIZABLEREAD COMMITTED

Standart, SERIALIZABLEişlemlerin varsayılan olmasını gerektirir , ancak çoğu ürün bunu yapmaz, çünkü daha gevşek işlem yalıtım düzeylerinde bir performansa neden olur. Bazı ürünler SERIALIZABLEseçildiğinde bile gerçekten seri hale getirilebilir işlemler sağlamamaktadır - özellikle de Oracle ve 9.1'den önceki PostgreSQL sürümleri. Ancak gerçek SERIALIZABLEişlemlerin kullanılması, yarış koşullarından şaşırtıcı etkilerden kaçınmanın tek yoludur ve yarış koşullarından SERIALIZABLEkaçınmak için işlemler her zaman engellemeli veya gelişen bir yarış koşulunu önlemek için bazı işlemleri geri almalıdır. SERIALIZABLEİşlemlerin en yaygın şekilde uygulanması , hem engelleme hem de serileştirme başarısızlıklarına sahip (kilitlenme şeklinde) Sıkı İki Fazlı Kilitleme'dir (S2PL).

Tam açıklama: Serializable Snapshot Isolation adlı yeni bir teknik kullanarak PostgreSQL sürüm 9.1'e gerçekten seri hale getirilebilir işlemler eklemek için Dan Port of MIT ile çalıştım.


Bu işi yapmanın gerçekten ucuz (peynirli?) Bir yolunu INSERT'in takip ettiği iki SİLME yapmaktan mı merak ediyorum. Sınırlı (2 konu) testimde işe yaramadı ancak bunun birçok konu için geçerli olup olmadığını görmek için daha fazla test etmeniz gerekiyor.
DaveyBob

READ COMMITTEDİşlemleri kullandığınız sürece , bir yarış koşulunuz vardır: ilk işlemden sonra DELETEve ikinci DELETEbaşlamadan önce başka bir işlem yeni bir satır eklerse ne olur ? Daha az sıkı işlemler ile SERIALIZABLEyakın yarış koşullarına iki ana yollardan olan promosyon (satır siliniyor zaman ama bu yardımcı olmuyor) bir çatışma ve somutlaşması çatışması. Çatışmayı, silinen her satır için güncellenmiş bir "id" tablosu oluşturarak veya açıkça tabloyu kilitleyerek gerçekleştirebilirsiniz. Ya da hatalı denemeleri kullanın.
kgrittn

Tekrar deniyor. Değerli içgörü için çok teşekkürler!
DaveyBob

21

PostgreSQL 9.2 için okumaya dayalı izolasyon seviyesinin açıklamasına göre, bunun tasarım gereği olduğuna inanıyorum :

GÜNCELLEME, SİLME, GÜNCELLEME İÇİN SEÇ ve PAYLAŞIM İÇİN SEÇ komutları, hedef satırları arama açısından SELECT ile aynı şekilde hareket eder: yalnızca komut başlangıç ​​zamanı 1 olarak işlenen hedef satırları bulurlar . Bununla birlikte, böyle bir hedef satır, bulununcaya kadar başka bir eşzamanlı işlem tarafından zaten güncellenmiş (veya silinmiş veya kilitlenmiş) olabilir. Bu durumda, güncelleyici güncelleyici ilk güncelleme işleminin (devam etmekte olan devam ediyorsa) taahhüt veya geri alınmasını bekleyecektir. İlk güncelleyici geri gelirse, etkileri olumsuzlanır ve ikinci güncelleyici başlangıçta bulunan satırı güncellemeye devam edebilir. İlk güncelleyici işlerse ilk güncelleyici sildiyseniz, ikinci güncelleyici satır yok sayacak 2Aksi halde, işlemin satırın güncellenmiş sürümüne uygulanması denenir.

İçinde eklemek satır S1zaman henüz mevcut değildi S2's DELETEbaşladı. Bu nedenle, yukarıdaki S2( 1 ) 'e göre yapılan silme ile görülmez . Bir S1silinmiş tarafından göz ardı edilir S2sitesindeki DELETEgöre ( 2 ).

Böylece S2, silme hiçbir şey yapmaz. İnsert olsa birlikte geldiğinde, kimse bu does bkz S1'ın insert:

Okuma Taahhüt modu her komutu, o ana kadar işlenen tüm işlemleri içeren yeni bir anlık görüntüyle başlattığından , aynı işlemdeki sonraki komutlar, her durumda işlenen eşzamanlı işlemin etkilerini görür . Yukarıdaki sorun, tek bir komutun veritabanının tamamen tutarlı bir görünümünü görüp görmeyeceğidir.

Bu yüzden deneme girişimi S2sınırlama ihlaliyle başarısız oldu.

Bu dokümanı okumaya devam etmek, tekrarlanabilir bir okuma veya seri hale getirilebilir bile kullanmak sorununuzu tamamen çözmez - ikinci oturum silme işleminde bir serileştirme hatasıyla başarısız olur.

Bu işlem yine denemenizi sağlar.


Teşekkürler Mat. Bu ne olduğu gibi görünmekle birlikte, bu mantıkta bir kusur var gibi görünüyor. Bana öyle geliyor ki, bir READ_COMMITTED iso düzeyinde, o zaman bu iki ifadenin bir tx içinde başarılı olması gerekir : Testten SİLME İLE NEREDE KİMLİĞİ = 1 DEĞERLERE Sınama (1) Demek istediğim, eğer satırı siler ve sonra satırı eklerim, o zaman bu ekleme başarılı olmalı. SQLServer bu hakkı alır. Olduğu gibi, her iki veritabanında da çalışması gereken bir üründe bu durumla başa çıkmakta çok zorlanıyorum.
DaveyBob

11

@ Mat'ın mükemmel cevabı ile tamamen aynı fikirdeyim . Sadece başka bir cevap yazıyorum, çünkü bir yorumda bulunmayacaktı.

Yorumunuza cevap olarak: DELETES2'de zaten belirli bir satır sürümünde bağlı. Bu arada S1 tarafından öldürüldüğünden, S2 kendini başarılı görüyor. Hızlı bir bakıştan anlaşılmasa da, olaylar dizisi aslında şöyle:

   S1 SİLME başarılı  
S2 DELETE (proxy tarafından başarılı - S1'den DELETE)  
   S1 yeniden INSERT değerleri bu arada neredeyse silindi  
S2 INSERT benzersiz anahtar kısıtlama ihlaliyle başarısız oldu

Hepsi tasarım gereği. Gereksinimleriniz için SERIALIZABLEişlemleri gerçekten kullanmanız ve seri hale getirme arızasında yeniden denediğinizden emin olmanız gerekir.


1

DEFERRABLE birincil anahtar kullanın ve tekrar deneyin.


bahşiş için teşekkürler, fakat DEFERRABLE'ı kullanmak hiçbir fark yaratmadı. Doktor olması gerektiği gibi okuyor , ancak yazmıyor.
DaveyBob

-2

Biz de bu sorunla karşılaştık. Bizim çözümümüz daha select ... for updateönce ekliyor delete from ... where. İzolasyon seviyesi Okuma Taahhütlü olmalıdır.

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.