postgresql - 永远运行大量记录的功能

标签 postgresql function concurrency sql-update plpgsql

我在 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/

相关文章:

scala - 如何等待多个 future ?

sql - 与 join 一起使用时,Case 返回多个值

sql - 3级嵌套排序

sql - 如何获取下表中每个最小值和最大值对应的行号

c++ - "template<class C> void mini(C &a4, C b4) { a4 = min(a4, b4); }"定义是什么意思?

java - 执行者没有按预期处理任务

linux - postgresql 不随 Linux 启动

c++ - 向 typedef 添加函数

Javascript函数定义和函数调用区别

c# - 如何优雅地卸载有线程运行的子 AppDomain