我在Postgres 9.3.5中创建了以下函数:
CREATE OR REPLACE FUNCTION get_result(val1 text, val2 text)
RETURNS text AS
$BODY
$Declare
result text;
BEGIN
select min(id) into result from table
where id_used is null and id_type = val2;
update table set
id_used = 'Y',
col1 = val1,
id_used_date = now()
where id_type = val2
and id = result;
RETURN result;
END;
$BODY$
LANGUAGE plpgsql VOLATILE COST 100;
当我在超过1000个或更多记录的循环中运行此函数时,它只是冻结并且只是说"查询正在运行"。当我查看我的表时,没有任何更新。当我运行一两个记录时,运行正常。
运行时的功能示例:
select get_result('123','idtype');
表格列:
id character varying(200),
col1 character varying(200),
id_used character varying(1),
id_used_date timestamp without time zone,
id_type character(200)
id
是表索引。
有人可以帮忙吗?
答案 0 :(得分:2)
很可能您遇到了竞争条件。当你在单独的交易中快速连续运行你的功能1000次时,会发生这样的事情:
T1 T2 T3 ...
SELECT max(id) -- id 1
SELECT max(id) -- id 1
SELECT max(id) -- id 1
...
Row id 1 locked, wait ...
Row id 1 locked, wait ...
UPDATE id 1
...
COMMIT
Wake up, UPDATE id 1 again!
COMMIT
Wake up, UPDATE id 1 again!
COMMIT
...
大部分重写并简化为SQL函数:
CREATE OR REPLACE FUNCTION get_result(val1 text, val2 text)
RETURNS text AS
$func$
UPDATE table t
SET id_used = 'Y'
, col1 = val1
, id_used_date = now()
FROM (
SELECT id
FROM table
WHERE id_used IS NULL
AND id_type = val2
ORDER BY id
LIMIT 1
FOR UPDATE -- lock to avoid race condition! see below ...
) t1
WHERE t.id_type = val2
-- AND t.id_used IS NULL -- repeat condition (not if row is locked)
AND t.id = t1.id
RETURNING id;
$func$ LANGUAGE sql;
相关问题有更多解释:
不要运行两个单独的SQL语句。这种情况更加昂贵,并且扩大了竞争条件的时间范围。一个带有子查询的UPDATE
要好得多。
您不需要PL / pgSQL来完成简单的任务。你仍然可以使用PL / pgSQL,UPDATE
保持不变。
您需要锁定所选行以抵御竞争条件。但是你不能使用你所使用的聚合函数来执行此操作,因为per documentation:
锁定子句不能在返回行的上下文中使用 无法用单个表格行清楚地识别;例如 不能与汇总一起使用。
大胆强调我的。幸运的是,您可以使用我在上面提供的等效min(id)
/ ORDER BY
轻松替换LIMIT 1
。也可以使用索引。
如果表格较大,则至少需要一个id
的索引。假设id
已被编入索引为PRIMARY KEY
,那将有所帮助。但是这个额外的partial multicolumn index可能会帮助更多:
CREATE INDEX foo_idx ON table (id_type, id)
WHERE id_used IS NULL;
咨询锁可能是这里的优秀方法:
或者您可能希望一次锁定多行: