Aynı işleve eşzamanlı çağrılar: kilitlenme nasıl gerçekleşiyor?


15

İşlevim new_customer, bir web uygulaması tarafından saniyede birkaç kez (ancak oturum başına bir kez) çağrılır. customerYaptığı ilk şey masayı kilitlemektir (eğer 'yoksa' bir ekleme yapmak - basit bir varyantı upsert).

Dokümanlar hakkındaki anlayışım, diğer new_customertüm çağrıların önceki tüm çağrılar bitene kadar sıraya girmesi gerektiğidir:

LOCK TABLE, gerektiğinde çakışan kilitlerin serbest bırakılmasını bekleyen bir tablo düzeyinde kilit alır.

Neden bazen tıkanıyor?

tanım:

create function new_customer(secret bytea) returns integer language sql 
                security definer set search_path = postgres,pg_temp as $$
  lock customer in exclusive mode;
  --
  with w as ( insert into customer(customer_secret,customer_read_secret)
              select secret,decode(md5(encode(secret, 'hex')),'hex') 
              where not exists(select * from customer where customer_secret=secret)
              returning customer_id )
  insert into collection(customer_id) select customer_id from w;
  --
  select customer_id from customer where customer_secret=secret;
$$;

günlük hatası:

2015-07-28 08:02:58 BST DETAYI: Süreç 12380, 12141 veritabanının 16438 ilişkisinde ExclusiveLock'u bekler; işlem 12379 tarafından engellendi.
        İşlem 12379, veritabanı 12141'in 16438 ilişkisinde ExclusiveLock'u bekler; 12380 işlemi tarafından engellendi.
        İşlem 12380: new_customer'ı seçin (kod çözme ($ 1 :: metin, 'hex'))
        Proses 12379: new_customer seçin (kod çözme ($ 1 :: metin, 'hex'))
2015-07-28 08:02:58 BST İPUCU: Sorgu ayrıntıları için sunucu günlüğüne bakın.
2015-07-28 08:02:58 BST CONTEXT: SQL işlevi "new_customer" deyimi 1
2015-07-28 08:02:58 BST BİLDİRİMİ: new_customer seçin (kod çözme ($ 1 :: metin, 'hex'))

ilişkisi:

postgres=# select relname from pg_class where oid=16438;
┌──────────┐
 relname  
├──────────┤
 customer 
└──────────┘

Düzenle:

Basit bir tekrarlanabilir test senaryosu almayı başardım. Bana göre bu bir çeşit yarış durumu nedeniyle bir hata gibi görünüyor.

şema:

create table test( id serial primary key, val text );

create function f_test(v text) returns integer language sql security definer set search_path = postgres,pg_temp as $$
  lock test in exclusive mode;
  insert into test(val) select v where not exists(select * from test where val=v);
  select id from test where val=v;
$$;

bash betiği iki bash oturumunda eşzamanlı olarak çalışır:

for i in {1..1000}; do psql postgres postgres -c "select f_test('blah')"; done

hata günlüğü (genellikle 1000 çağrıda bir avuç çıkmaz):

2015-07-28 16:46:19 BST ERROR:  deadlock detected
2015-07-28 16:46:19 BST DETAIL:  Process 9394 waits for ExclusiveLock on relation 65605 of database 12141; blocked by process 9393.
        Process 9393 waits for ExclusiveLock on relation 65605 of database 12141; blocked by process 9394.
        Process 9394: select f_test('blah')
        Process 9393: select f_test('blah')
2015-07-28 16:46:19 BST HINT:  See server log for query details.
2015-07-28 16:46:19 BST CONTEXT:  SQL function "f_test" statement 1
2015-07-28 16:46:19 BST STATEMENT:  select f_test('blah')

düzenleme 2:

@ypercube bir varyant önerdi ile lock tabledışarıdan fonksiyonu:

for i in {1..1000}; do psql postgres postgres -c "begin; lock test in exclusive mode; select f_test('blah'); end"; done

ilginç bir şekilde bu çıkmazları ortadan kaldırır.


2
Aynı işlemde, bu işleve girmeden önce, customerdaha zayıf bir kilit tutacak şekilde kullanılır? Sonra bir kilit yükseltme sorunu olabilir.
Daniel Vérité

2
Bunu açıklayamam. Daniel'in bir anlamı olabilir. Bunu pgsql-general'de yükseltmeye değer olabilir. Her iki durumda da, gelecek Postgres 9.5'teki UPSERT uygulamasının farkında mısınız? Depesz ona bir göz atıyor.
Erwin Brandstetter

2
Yani aynı işlem içinde, sadece aynı oturumda değil (kilitler tx sonunda serbest bırakıldığı için). @Alexk'in cevabı düşündüğüm şey, ama eğer tx fonksiyonla başlayıp biterse, bu çıkmazı açıklayamaz.
Daniel Vérité

1
@Erwin şüphesiz pgsql-
bugs'a

2
Gerçekten çok ilginç. Beklendiği gibi çalışan benzer plpgsql vakaları hatırladığım gibi, bu da plpgsql çalışır.
Erwin Brandstetter

Yanıtlar:


10

Bunu gönderdim pgsql-bugs'e ve Tom Lane'den gelen yanıt , bunun SQL dil işlevlerinin işlenme şeklinin mekaniği tarafından gizlenen bir kilit yükseltme sorunu olduğunu gösteriyor. Esasen, tarafından üretilen kilit, masadaki özel kilitten önceinsert elde edilir :

Bu sorun bir SQL fonksiyonu aynı anda tüm işlev gövdesi için ayrıştırma (ve belki de planlama; şimdi kodu kontrol etmek gibi hissetmiyorum) yapacağına inanıyorum. Bu, INSERT komutu nedeniyle, LOCK komutu gerçekten yürütülmeden önce işlev gövdesi ayrıştırma işlemi sırasında "test" tablosunda RowExclusiveLock'u aldığınız anlamına gelir. Dolayısıyla, KİLİT kilit yükseltme girişimini temsil eder ve kilitlenmeler beklenmelidir.

Bu kodlama tekniği plpgsql'de güvenlidir, ancak SQL dili işlevinde güvenli değildir.

Ayrıştırmanın her seferinde bir ifade olması için SQL dili işlevlerinin yeniden uygulanmasıyla ilgili tartışmalar olmuştur, ancak bu yönde olan bir şey hakkında nefesini tutmayın; hiç kimse için yüksek öncelikli bir endişe gibi görünmüyor.

selamlar, tom lane

Bu ayrıca, bir sarma plpgsql bloğunda ( @ypercube tarafından önerildiği gibi) tablonun fonksiyonun dışında kilitlenmesinin neden kilitlenmeleri önlediğini açıklar.


3
İnce nokta: ypercube aslında düz SQL kilit test ile açık işlem dışında bir fonksiyonu değil , bir aynı plpgsql bloğu.
Erwin Brandstetter

1
Oldukça doğru, benim hatam. Sanırım denediğimiz başka bir şeyle karışıyordum (ki bu kilitlenmeyi engellemedi).
Jack diyor ki topanswers.xyz

4

New_customer öğesini aramadan önce başka bir deyim çalıştırdığınızı ve bunların çakışan bir kilit aldığını varsayarsak EXCLUSIVE(temel olarak, müşteri tablosundaki herhangi bir veri değişikliği), açıklama çok basittir.

Problem basit bir örnekle yeniden üretilebilir (bir işlev dahil bile değil):

CREATE TABLE test(id INTEGER);

1. oturum:

BEGIN;

INSERT INTO test VALUES(1);

2. oturum

BEGIN;
INSERT INTO test VALUES(1);
LOCK TABLE test IN EXCLUSIVE MODE;

1. oturum

LOCK TABLE test IN EXCLUSIVE MODE;

İlk oturum eki yaptığında, ROW EXCLUSIVEbir tablodaki kilidi alır. Bu arada, oturum 2 de ROW EXCLUSIVEkilidi alır ve bir EXCLUSIVEkilit almaya çalışır . Hangi noktada EXCLUSIVEkilit ile çakıştığından 1. oturumu beklemek zorundadır ROW EXCLUSIVE. Sonunda, 1. oturum köpekbalıkları atlar ve birEXCLUSIVE kilit , ancak kilitler sırayla elde edildiğinden, 2. oturumdan sonra sıraya girer. Bu da birincisi için bir çıkmaza neden olur:

DETAIL:  Process 28514 waits for ExclusiveLock on relation 58331454 of database 44697822; blocked by process 28084.
Process 28084 waits for ExclusiveLock on relation 58331454 of database 44697822; blocked by process 28514

Bu sorunun çözümü, kilitleri olabildiğince erken, genellikle bir işlemdeki ilk şey olarak elde etmektir. Öte yandan, PostgreSQL iş yükünün çok nadir durumlarda sadece kilitlere ihtiyacı vardır, bu yüzden upert yapma şeklinizi yeniden düşünmenizi öneririm (bu makaleye bir göz atın http://www.depesz.com/2012/06/10 / neden-up-up-çok karmaşık / ).


2
Tüm bunlar ilginç, ama db günlüklerindeki mesaj şöyle bir şey okurdu: Process 28514 : select new_customer(decode($1::text, 'hex')); Process 28084 : BEGIN; INSERT INTO test VALUES(1); select new_customer(decode($1::text, 'hex'))Jack az önce: Process 12380: select new_customer(decode($1::text, 'hex')) Process 12379: select new_customer(decode($1::text, 'hex'))- fonksiyon çağrısının her iki işlemde de ilk komut olduğunu belirtir (bir şey eksik olmadıkça).
Erwin Brandstetter

Teşekkürler ve söylediklerinize katılıyorum, ancak bu durumda böyle görünmüyor. Soruya eklediğim daha minimal test durumunda bu daha açıktır (ki bunu kendiniz deneyebilirsiniz).
Jack diyor ki topanswers.xyz

2
Aslında , mekanizmanın ince olmasına rağmen kilit yükselmesi konusunda haklı olduğunuz ortaya çıkıyor .
Jack diyor ki topanswers.xyz
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.