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: