功能永远运行大量的记录

时间:2015-03-28 17:30:29

标签: 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是表索引。

有人可以帮忙吗?

1 个答案:

答案 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;
    

替代解决方案

咨询锁可能是这里的优秀方法:

或者您可能希望一次锁定多行