【问题标题】:Function taking forever to run for large number of records函数需要永远运行大量记录
【发布时间】:2015-06-01 22:31:57
【问题描述】:

我在 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 是表索引。

有人可以帮忙吗?

【问题讨论】:

  • 使用 explain 关键字运行您的选择,您将看到它进行表扫描。因此,尽可能在使用聚合函数时应用适当的索引并使用 GROUP BY 子句。
  • 您的表不能称为table$BODY $ 中的换行符也不能。而是发布您的实际功能。您发布的内容可能隐藏了问题。同样重要的是:如何准确地循环?

标签: postgresql function concurrency sql-update plpgsql


【解决方案1】:

您很可能遇到了竞争条件。当您在单独的事务中快速连续运行函数 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;
    

替代解决方案

咨询锁在这里可能是更好的方法:

或者您可能想要一次锁定多行

【讨论】:

  • @King:您可能也对我添加的链接感兴趣。此外,如果这回答了您的问题,请考虑接受答案。
猜你喜欢
  • 2023-03-12
  • 2019-12-08
  • 1970-01-01
  • 1970-01-01
  • 2016-01-31
  • 2014-11-23
  • 1970-01-01
  • 1970-01-01
  • 2020-04-03
相关资源
最近更新 更多