new_customer
Web应用程序每秒调用我的函数几次(但每个会话仅调用一次)。它要做的第一件事就是锁定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等待数据库12141的关系16438上的ExclusiveLock;被进程12379阻止。 进程12379在数据库12141的关系16438上等待ExclusiveLock;被进程12380阻止。 流程12380:选择new_customer(decode($ 1 :: text,'hex')) 流程12379:选择new_customer(decode($ 1 :: text,'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(decode($ 1 :: text,'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;
$$;
bash脚本在两个bash会话中同时运行:
for i in {1..1000}; do psql postgres postgres -c "select f_test('blah')"; done
错误日志(通常是1000个调用中的少数死锁):
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
使用方式会抢到较弱的锁?那么这可能是一个锁升级问题。