Postgres GÜNCELLEME… SINIR 1


77

Sunucu durumu ('etkin', 'bekleme' vb.) Gibi sunucu kümelerinin ayrıntılarını içeren bir Postgres veritabanına sahibim. Etkin sunucuların herhangi bir zamanda bir bekleme moduna geçmesi gerekmeyebilir ve özellikle hangi bekleme modunun kullanıldığını umursamıyorum.

Bir veritabanı sorgusunun bekleme durumunu (SADECE BİR) değiştirmesi ve kullanılacak sunucu IP'sini döndürmesini istiyorum. Seçim isteğe bağlı olabilir: sunucunun durumu sorgu ile değiştiğinden, hangi bekleme modunun seçildiği önemli değildir.

Sorgumu tek bir güncellemeyle sınırlandırmak mümkün mü?

İşte şu ana kadar sahip olduğum şey:

UPDATE server_info SET status = 'active' 
WHERE status = 'standby' [[LIMIT 1???]] 
RETURNING server_ip;

Postgres bundan hoşlanmaz. Ne farklı yapabilirdim?


Sunucuyu kodda seçin ve sınırlandırılmış bir yere ekleyin. Bu, yine de ilk olarak kontrol edilmiş ek koşullara (en eski, en yeni, en yeni canlı, en az yüklü, aynı dc, farklı raf, en az hatalara) izin verir. Yük devretme protokollerinin çoğu, yine de bir şekilde determinizm gerektirir.
Eckes

@eckes Bu ilginç bir fikir. Benim durumumda "sunucuyu kodda seçmek" ilk önce db'deki mevcut sunucuların bir listesini okumak ve ardından bir kaydı güncellemek anlamına gelirdi . Uygulamanın birçok örneği bu işlemi gerçekleştirebildiği için, bir yarış durumu var ve bir atomik operasyon gerekli (ya da 5 yıl önceydi). Seçimin deterministik olmasına gerek yoktu.
vastlysuperiorman

Yanıtlar:


125

Eşzamanlı yazma erişimi olmadan

Bir CTE'deki bir seçimi gerçekleştirin ve onunla birlikte verilen FROMmaddeye katılın UPDATE.

WITH cte AS (
   SELECT server_ip          -- pk column or any (set of) unique column(s)
   FROM   server_info
   WHERE  status = 'standby'
   LIMIT  1                  -- arbitrary pick (cheapest)
   )
UPDATE server_info s
SET    status = 'active' 
FROM   cte
WHERE  s.server_ip = cte.server_ip
RETURNING server_ip;

Başlangıçta burada düz bir alt sorgu vardı, ancak bu Feike'ın belirttiği LIMITgibi bazı sorgu planları için engel olabilir :

Planlayıcı, LIMITingalt sorgu üzerinde iç içe bir döngü uygulayan ve örneğin şunlardan daha UPDATEsfazlasına neden olan bir plan oluşturmayı seçebilir LIMIT:

 Update on buganalysis [...] rows=5
   ->  Nested Loop
         ->  Seq Scan on buganalysis
         ->  Subquery Scan on sub [...] loops=11
               ->  Limit [...] rows=2
                     ->  LockRows
                           ->  Sort
                                 ->  Seq Scan on buganalysis

Test vakasının çoğaltılması

Yukarıdakileri sabitlemenin yolu, LIMITalt sorguyu kendi CTE'sine sarmaktı, CTE gerçekleştiği için, iç içe geçmiş döngünün farklı yinelemelerinde farklı sonuçlar vermeyecektir.

Veya basit vaka içindüşük korelasyonlu bir alt sorgu kullanınLIMIT 1. Daha basit, daha hızlı:

UPDATE server_info
SET    status = 'active' 
WHERE  server_ip = (
         SELECT server_ip
         FROM   server_info
         WHERE  status = 'standby'
         LIMIT  1
         )
RETURNING server_ip;

Eşzamanlı yazma erişimi ile

Tüm bunlar için varsayılan izolasyon seviyesiniREAD COMMITTED varsayarak . Daha katı izolasyon seviyeleri ( REPEATABLE READve SERIALIZABLE) hala serileştirme hatalarına neden olabilir. Görmek:

Eşzamanlı yazma yükü altında, FOR UPDATE SKIP LOCKEDyarış koşullarını önlemek için satırı kilitlemek için ekleyin . SKIP LOCKEDPostgres 9.5'e eklenmiş , eski sürümler için aşağıya bakınız. Kullanım kılavuzu:

İle SKIP LOCKED, hemen kilitlenemeyen seçilen satırlar atlanır. Kilitli satırları atlamak verilerin tutarsız bir görünümünü sağlar, bu nedenle bu genel amaçlı çalışma için uygun değildir, ancak sıra benzeri bir tabloya erişen birden fazla tüketiciyle kilit çekişmesini önlemek için kullanılabilir.

UPDATE server_info
SET    status = 'active' 
WHERE  server_ip = (
         SELECT server_ip
         FROM   server_info
         WHERE  status = 'standby'
         LIMIT  1
         FOR    UPDATE SKIP LOCKED
         )
RETURNING server_ip;

Kalifiye olmayan, kilitlenmemiş satır kalmadıysa, bu sorguda hiçbir şey olmaz (satır güncellenmez) ve boş bir sonuç alırsınız. Kritik olmayan işlemler için bu bittiği anlamına gelir.

Ancak, eşzamanlı işlemler satırları kilitlemiş olabilir, ancak güncellemeyi ( ROLLBACKveya diğer nedenlerle) bitirmeyin . Son kontrolün yapıldığından emin olmak için :

SELECT NOT EXISTS (
   SELECT 1
   FROM   server_info
   WHERE  status = 'standby'
   );

SELECTayrıca kilitli satırları da görür. Geri dönüşü olmayan truebir veya daha fazla satır hala işleniyor ve işlemler yine de geri alınabiliyor. (Veya bu sırada yeni satırlar eklendi.) Biraz bekleyin, ardından iki adımı tekrar çevirin: ( UPDATEgeri satır SELECTalamayana kadar ; ...) elde edene kadar true.

İlgili:

Olmadan SKIP LOCKEDPostgreSQL 9.4 veya daha eski

UPDATE server_info
SET    status = 'active' 
WHERE  server_ip = (
         SELECT server_ip
         FROM   server_info
         WHERE  status = 'standby'
         LIMIT  1
         FOR    UPDATE
         )
RETURNING server_ip;

Aynı satırı kilitlemeye çalışan eşzamanlı işlemler, ilki kilitleninceye kadar engellenir.

Birincisi geri alınmışsa, bir sonraki işlem kilidi alır ve normal şekilde ilerler; sıradaki diğerleri beklemeye devam ediyor.

İlk kabul edilirse, WHEREdurum yeniden değerlendirilir ve artık değişmediyse TRUE( statusdeğişmişse) CTE (biraz şaşırtıcı şekilde) hiç satır döndürmez. Hiçbir şey olmuyor. Tüm işlemler güncellemek istediğinizde bu istenen davranıştır aynı satır . Ama her işlem güncelleştirmek istiyor değilken sonraki satır . Ve sadece rastgele (veya rastgele ) bir sırayı güncellemek istediğimiz için , beklemenin bir anlamı yok.

Danışma kilitleri yardımı ile durumu engelleyebiliriz :

UPDATE server_info
SET    status = 'active' 
WHERE  server_ip = (
         SELECT server_ip
         FROM   server_info
         WHERE  status = 'standby'
         AND    pg_try_advisory_xact_lock(id)
         LIMIT  1
         FOR    UPDATE
         )
RETURNING server_ip;

Bu şekilde, bir sonraki satır henüz kilitlenmemiş olarak güncellenecektir. Her işlem çalışmak için yeni bir satır alır. Bu numara için Çek Postgres Wiki'den yardım aldım .

idherhangi bir benzersiz olan bigintkolonu (ya da bu gibi bir örtülü döküm ile her tür int4ya da int2).

Danışma kilitleri veritabanınızdaki birden fazla tablo için aynı anda kullanılıyorsa, burada pg_try_advisory_xact_lock(tableoid::int, id)- idbenzersiz olmakla ilgili kesin integer.
Yana tableoidbir olan bigintmiktar, teorik olarak taşma olabilir integer. Yeterince paranoyak kullanıyorsanız, (tableoid::bigint % 2147483648)::intbunun yerine kullanın - gerçekten paranoyak için teorik bir "karma çarpışma" bırakarak ...

Ayrıca, Postgres WHEREkoşulları herhangi bir sırada test etmek için ücretsizdir . Bu olabilir test etmek pg_try_advisory_xact_lock()ve kilit elde önce status = 'standby' ilgisiz satırlar, ek danışma kilitleri neden olabilecek şekilde, status = 'standby'doğru değildir. SO ile ilgili soru:

Genellikle, bunu görmezden gelebilirsiniz. To garanti yalnızca eleme satırlar kilitli göre, yuva yukarıdaki gibi bir CTE yüklem (lar) ya da bir alt sorgu olabilir OFFSET 0hack (satır içi uygulaması önler) . Örnek:

Veya (sıralı taramalar için daha ucuz) koşulları şöyle bir CASEifadeye yerleştirir:

WHERE  CASE WHEN status = 'standby' THEN pg_try_advisory_xact_lock(id) END

Ancak , CASEhile Postgres'in bir endeks kullanmasını engelleyecekti status. Eğer böyle bir indeks mevcutsa, başlamak için fazladan yuvalamaya ihtiyacınız yoktur: sadece kalifiye satırlar indeks taramasında kilitlenir.

Her aramada bir dizin kullanıldığından emin olamadığınız için aşağıdakileri yapabilirsiniz:

WHERE  status = 'standby'
AND    CASE WHEN status = 'standby' THEN pg_try_advisory_xact_lock(id) END

CASEMantıksal olarak gereksiz, ama sunucuların ele amaç.

Komut uzun bir işlemin parçasıysa, manuel olarak serbest bırakılabilen (ve bırakılması gereken) oturum düzeyinde kilitleri düşünün. Böylece kilitli satır ile işiniz biter bitmez kilidini açabilirsiniz: pg_try_advisory_lock()vepg_advisory_unlock() . Kullanım kılavuzu:

Oturum düzeyinde bir kez alındığında, açıkça bırakılana veya oturum sona erene kadar bir danışma kilidi tutulur.

İlgili:

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.