ฟังก์ชั่นของฉันnew_customer
ถูกเรียกหลายครั้งต่อวินาที (แต่เพียงครั้งเดียวต่อเซสชัน) โดยเว็บแอปพลิเคชัน สิ่งแรกที่มันทำคือล็อคcustomer
ตาราง (เพื่อทำการ 'แทรกถ้าไม่มี' - ตัวแปรง่าย ๆ ของupsert
)
ความเข้าใจของฉันเกี่ยวกับเอกสารก็คือการโทรอื่น ๆnew_customer
ควรเพียงแค่รอจนกว่าการโทรก่อนหน้านี้ทั้งหมดจะเสร็จสิ้น:
LOCK TABLE จะได้รับการล็อคระดับโต๊ะโดยรอถ้าจำเป็นเพื่อให้การปลดล็อกที่มีข้อขัดแย้งเกิดขึ้น
ทำไมบางครั้งมันถึงตายแทน
ความหมาย:
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;
$$;
ข้อผิดพลาดจากบันทึก:
2015-07-28 08:02:58 รายละเอียด BST: กระบวนการ 12380 รอสำหรับ ExclusiveLock ในความสัมพันธ์ 16438 ของฐานข้อมูล 12141; ถูกบล็อกโดยกระบวนการ 12379 กระบวนการ 12379 รอสำหรับ ExclusiveLock ในความสัมพันธ์ 16438 ของฐานข้อมูล 12141 ถูกบล็อกโดยกระบวนการ 12380 กระบวนการ 12380: เลือก new_customer (ถอดรหัส ($ 1 :: ข้อความ, 'hex')) กระบวนการ 12379: เลือก new_customer (ถอดรหัส ($ 1 :: ข้อความ 'hex') 2015-07-28 08:02:58 คำแนะนำ BST: ดูบันทึกเซิร์ฟเวอร์เพื่อดูรายละเอียดแบบสอบถาม 2015-07-28 08:02:58 BST บริบท: ฟังก์ชัน SQL "new_customer" คำสั่ง 1 2015-07-28 08:02:58 งบ BST: เลือก new_customer (ถอดรหัส ($ 1 :: ข้อความ, 'hex')
ความสัมพันธ์:
postgres=# select relname from pg_class where oid=16438;
┌──────────┐
│ relname │
├──────────┤
│ customer │
└──────────┘
แก้ไข:
ฉันจัดการเพื่อให้ได้กรณีทดสอบที่ทำซ้ำได้ง่าย สำหรับฉันนี่ดูเหมือนข้อผิดพลาดเนื่องจากสภาพการแข่งขันบางประเภท
สคี:
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;
$$;
สคริปต์ทุบตีทำงานพร้อมกันในสองช่วงเวลาทุบตี:
for i in {1..1000}; do psql postgres postgres -c "select f_test('blah')"; done
บันทึกข้อผิดพลาด (โดยทั่วไปจะมี deadlock จำนวนหนึ่งในการโทร 1,000 ครั้ง):
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')
แก้ไข 2:
@ypercube แนะนำตัวแปรที่มีlock table
ฟังก์ชันภายนอก:
for i in {1..1000}; do psql postgres postgres -c "begin; lock test in exclusive mode; select f_test('blah'); end"; done
ที่น่าสนใจนี้ช่วยลดการหยุดชะงัก
customer
ใช้ในลักษณะที่จะทำให้ล็อคอ่อนแอ อาจเป็นปัญหาการอัพเกรดล็อค