我在 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
是表索引。
有人可以帮忙吗?
最佳答案
很可能您遇到了竞争条件。当您在单独的事务中快速连续运行您的函数 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 :
The locking clauses cannot be used in contexts where returned rows cannot be clearly identified with individual table rows; for example they cannot be used with aggregation.
大胆强调我的。幸运的是,您可以轻松地将
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;
替代解决方案
Advisory locks 可能是这里更好的方法:
或者您可能希望一次锁定多行:
关于postgresql - 永远运行大量记录的功能,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29320296/