Eşzamanlı yazma erişimi olmadan
Bir CTE'deki bir seçimi gerçekleştirin ve onunla birlikte verilen FROM
maddeye 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 LIMIT
gibi bazı sorgu planları için engel olabilir :
Planlayıcı, LIMITing
alt sorgu üzerinde iç içe bir döngü uygulayan ve örneğin şunlardan daha UPDATEs
fazlası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, LIMIT
alt 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 READ
ve SERIALIZABLE
) hala serileştirme hatalarına neden olabilir. Görmek:
Eşzamanlı yazma yükü altında, FOR UPDATE SKIP LOCKED
yarış koşullarını önlemek için satırı kilitlemek için ekleyin . SKIP LOCKED
Postgres 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 ( ROLLBACK
veya 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'
);
SELECT
ayrıca kilitli satırları da görür. Geri dönüşü olmayan true
bir 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: ( UPDATE
geri satır SELECT
alamayana kadar ; ...) elde edene kadar true
.
İlgili:
Olmadan SKIP LOCKED
PostgreSQL 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, WHERE
durum yeniden değerlendirilir ve artık değişmediyse TRUE
( status
değ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 .
id
herhangi bir benzersiz olan bigint
kolonu (ya da bu gibi bir örtülü döküm ile her tür int4
ya 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)
- id
benzersiz olmakla ilgili kesin integer
.
Yana tableoid
bir olan bigint
miktar, teorik olarak taşma olabilir integer
. Yeterince paranoyak kullanıyorsanız, (tableoid::bigint % 2147483648)::int
bunun yerine kullanın - gerçekten paranoyak için teorik bir "karma çarpışma" bırakarak ...
Ayrıca, Postgres WHERE
koş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 0
hack (satır içi uygulaması önler) . Örnek:
Veya (sıralı taramalar için daha ucuz) koşulları şöyle bir CASE
ifadeye yerleştirir:
WHERE CASE WHEN status = 'standby' THEN pg_try_advisory_xact_lock(id) END
Ancak , CASE
hile 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
CASE
Mantı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: