Fungsi saya new_customer
dipanggil beberapa kali per detik (tetapi hanya sekali per sesi) oleh aplikasi web. Hal pertama yang dilakukannya adalah mengunci customer
tabel (untuk melakukan 'masukkan jika tidak ada' — varian sederhana dari sebuah upsert
).
Pemahaman saya tentang dokumen adalah bahwa panggilan lain new_customer
harus cukup mengantri sampai semua panggilan sebelumnya selesai:
LOCK TABLE mendapatkan kunci tingkat meja, menunggu jika perlu untuk melepaskan kunci yang bertentangan.
Mengapa kadang-kadang menemui jalan buntu?
definisi:
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;
$$;
kesalahan dari log:
2015-07-28 08:02:58 Detail BST: Memproses 12380 menunggu ExclusiveLock pada relasi 16438 dari basis data 12141; diblokir oleh proses 12379. Proses 12379 menunggu ExclusiveLock pada relasi 16438 dari basis data 12141; diblokir oleh proses 12380. Proses 12380: pilih new_customer (decode ($ 1 :: text, 'hex')) Proses 12379: pilih new_customer (decode ($ 1 :: text, 'hex')) 2015-07-28 08:02:58 BST HINT: Lihat log server untuk detail kueri. 2015-07-28 08:02:58 BST CONTEXT: SQL function "new_customer" pernyataan 1 2015-07-28 08:02:58 PERNYATAAN BST: pilih new_customer (decode ($ 1 :: text, 'hex'))
hubungan:
postgres=# select relname from pg_class where oid=16438;
┌──────────┐
│ relname │
├──────────┤
│ customer │
└──────────┘
edit:
Saya sudah berhasil mendapatkan test case sederhana yang dapat direproduksi. Bagi saya ini terlihat seperti bug karena semacam kondisi balapan.
skema:
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;
$$;
skrip bash dijalankan secara bersamaan dalam dua sesi bash:
for i in {1..1000}; do psql postgres postgres -c "select f_test('blah')"; done
log kesalahan (biasanya segelintir kebuntuan selama 1000 panggilan):
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')
edit 2:
@ ypercube menyarankan varian dengan lock table
fungsi di luarnya:
for i in {1..1000}; do psql postgres postgres -c "begin; lock test in exclusive mode; select f_test('blah'); end"; done
Menariknya ini menghilangkan kebuntuan.
sumber
customer
digunakan dengan cara yang akan mengambil kunci yang lebih lemah? Maka itu bisa menjadi masalah peningkatan kunci.Jawaban:
Saya memposting ini ke pgsql-bug dan jawaban di sana dari Tom Lane menunjukkan ini adalah masalah eskalasi kunci, disamarkan oleh mekanisme cara fungsi bahasa SQL diproses. Pada dasarnya, kunci yang dihasilkan oleh
insert
diperoleh sebelum kunci eksklusif di atas meja :Ini juga menjelaskan mengapa mengunci tabel di luar fungsi dalam blok plpgsql pembungkus (seperti yang disarankan oleh @ypercube) mencegah kebuntuan.
sumber
Dengan asumsi Anda menjalankan pernyataan lain sebelum memanggil new_customer, dan mereka mendapatkan kunci yang bertentangan dengan
EXCLUSIVE
(pada dasarnya, setiap modifikasi data dalam tabel pelanggan), penjelasannya sangat sederhana.Seseorang dapat mereproduksi masalah dengan contoh sederhana (bahkan tidak termasuk fungsi):
Sesi 1:
Sesi 2
Sesi 1
Ketika sesi pertama melakukan penyisipan, ia memperoleh
ROW EXCLUSIVE
kunci di atas meja. Sementara itu, sesi 2 mencoba juga mendapatkanROW EXCLUSIVE
kunci, dan mencoba untuk mendapatkanEXCLUSIVE
kunci. Pada titik mana ia harus menunggu sesi 1, karenaEXCLUSIVE
kunci konflik denganROW EXCLUSIVE
. Akhirnya, sesi 1 melompat hiu dan mencoba untuk mendapatkanEXCLUSIVE
kunci, tetapi karena kunci diperoleh secara berurutan, ia akan mengantri setelah sesi ke-2. Ini, pada gilirannya, menunggu yang pertama, menghasilkan jalan buntu:Solusi untuk masalah ini adalah mendapatkan kunci sesegera mungkin, biasanya sebagai hal pertama dalam suatu transaksi. Di sisi lain, beban kerja PostgreSQL hanya perlu kunci dalam beberapa kasus yang sangat jarang, jadi saya sarankan memikirkan kembali cara Anda melakukan upert (lihat artikel ini http://www.depesz.com/2012/06/10 / why-is-upsert-so-ribet / ).
sumber
Process 28514 : select new_customer(decode($1::text, 'hex')); Process 28084 : BEGIN; INSERT INTO test VALUES(1); select new_customer(decode($1::text, 'hex'))
Sementara Jack baru saja:Process 12380: select new_customer(decode($1::text, 'hex')) Process 12379: select new_customer(decode($1::text, 'hex'))
- menunjukkan bahwa pemanggilan fungsi adalah perintah pertama dalam kedua transaksi (kecuali jika saya kehilangan sesuatu).