postgres锁表以避免读/写异常

时间:2017-05-22 17:55:07

标签: postgresql concurrency locking isolation-level

我有一个简单的表counter,如下所示。

CREATE TABLE counter
(
  id text NOT NULL,
  total integer NOT NULL,
  CONSTRAINT pk_id PRIMARY KEY (id)
)
WITH (
  OIDS=FALSE
);

counter.id有一组固定的20个值,我已经为所有20 total手动将初始counter.id设置为0。在其中一个存储过程中,我添加了以下行

UPDATE counter SET total = total + 1 WHERE id = 'SomeID';现在我看到了大量的could not serialize access due to read/write dependencies among transactions postgres例外情况。如果我评论这一行,问题就会消失。表counter并未在其他任何地方同时更新/读取,而是此行。

我在交易中使用ISOLATION级别SERIALIZABLE。数据访问层由Java Spring JDBC组成。我尝试了以下两种方法来解决这个问题。

  1. 在致电LOCK counter in ACCESS EXCLUSIVE MODE;之前使用UPDATE counter
  2. 使用PERFORM pg_advisory_xact_lock(1);在致电UPDATE counter之前。
  3. 我很惊讶两种方法都没有解决问题。从文档中,LOCK应该为一个线程提供对表counter的独占访问权,这应该阻止可序列化的异常。但它似乎没有用。

    有关我在这里做错了什么的建议吗?

    更新:所以这是我尝试在更简化的设置中重现问题。我有一个存储过程,如下所示。

    CREATE OR REPLACE FUNCTION public.testinsert() RETURNS void AS
    $BODY$
    BEGIN
        LOCK counter IN ACCESS EXCLUSIVE MODE;
        INSERT INTO counter("id", "total")
        VALUES('SomeID', 0) ON CONFLICT(id)
        DO UPDATE SET total = counter.total + 1 where counter.id = 'SomeID';
    END;
    $BODY$
    LANGUAGE plpgsql VOLATILE
    COST 100;
    ALTER FUNCTION public.testinsert()
    

    现在我在两个独立的psql控制台中尝试以下操作。

    Console1: begin transaction isolation level serializable;
    Console2: begin transaction isolation level serializable;
    Console1: select testInsert();
    Console2: select testInsert();
    Console1: commit;
    

    此时Console2会抛出异常could not serialize access due to concurrent update。这清楚地告诉我,当放置在存储过程中时,锁定计数器不起作用。有什么想法吗?

    如果我尝试使用Console1Console2并在lock counter之后立即执行begin transaction,然后调用存储过程,那么代码就可以了因为Console2现在等待锁定。

    我尝试用PERFORM pg_advisory_xact_lock(1)替换锁定计数器并遇到类似的问题。

1 个答案:

答案 0 :(得分:1)

https://www.postgresql.org/docs/current/static/transaction-iso.html的文档中考虑这句话,关于SERIALIZABLE

  

使用此级别的应用程序必须准备好重试事务   由于序列化失败。

看起来你忽略了那部分,但你不应该。

有关会话可能必须处理的序列化失败的各种示例,另请参阅https://wiki.postgresql.org/wiki/SSI

发生这些失败是这种隔离级别的关键。如果您根本不想要它们,则应该使用不太严格的隔离,或者通过明确锁定其他会话来避免并发,例如在pg_advisory_xact_lock(1)中,但是在整个事务的最开始。< / p>

您添加的UPDATE可能只会更改并发事务的执行时间,因为它会创建锁定(某些事务现在在它们之前不会停止)。这足以触发序列化错误。根本原因是您的并发事务在大约相同的位置同时读/写数据(同时意味着:事务重叠,不一定是写入恰好在同一时钟壁时间发生)。

上述链接页面底部的这些提示也可能有助于降低序列化失败的可能性:

  

在依赖Serializable事务时获得最佳性能   并发控制,应该考虑这些问题:

     
      
  • 在可能的情况下将事务声明为READ。

  •   
  • 如果需要,使用连接池控制活动连接数。这始终是一个重要的性能考虑因素,但是   在使用Serializable的繁忙系统中,它尤为重要   交易。

  •   
  • 为了完整性目的,不要在单个交易中投入更多资金。

  •   
  • 不要在交易中闲置&#34;空闲&#34;超过必要的时间。

  •   
  • 消除由于自动提供保护而不再需要的显式锁,SELECT FOR UPDATE和SELECT FOR SHARE   通过Serializable交易。

  •   
  • 当系统被强制将多个页面级谓词锁组合成单个关系级谓词锁时,因为   谓词锁表是内存不足,速度增加的   可能会发生序列化失败。你可以通过增加避免这种情况   max_pred_locks_per_transaction。

  •   
  • 顺序扫描总是需要关系级谓词锁定。这可以导致序列化率的增加   故障。鼓励使用索引扫描可能会有所帮助   减少random_page_cost和/或增加cpu_tuple_cost。务必   权衡任何事务回滚的减少和重新启动   查询执行时间的整体变化。

  •