AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • 主页
  • 系统&网络
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • 主页
  • 系统&网络
    • 最新
    • 热门
    • 标签
  • Ubuntu
    • 最新
    • 热门
    • 标签
  • Unix
    • 最新
    • 标签
  • DBA
    • 最新
    • 标签
  • Computer
    • 最新
    • 标签
  • Coding
    • 最新
    • 标签
主页 / dba / 问题 / 80720
Accepted
arthur
arthur
Asked: 2014-10-22 01:46:14 +0800 CST2014-10-22 01:46:14 +0800 CST 2014-10-22 01:46:14 +0800 CST

Postgres 9.1 中的 ACID 违规?

  • 772

我正在使用 Postgres 数据库为大量计算机/进程实施作业调度。简而言之,每个作业都有它的 ID,所有调度都是通过三个选项卡实现的:所有作业、当前正在运行的作业和已经完成的作业。

调度的关键功能是 (1) 请求作业和 (2) 通知数据库有关已完成的作业。请求作业从作业列表中获取任何 id,它不在 running 表中,也不在 completed 表中:

insert into piper.jobs_running
select x.fid from ( 
  SELECT fid FROM piper.jobs
  except 
  select fid from piper.jobs_running
  except 
  select fid from piper.jobs_completed
 ) as x limit 1
returning(fid)

完成作业会将其从运行列表中删除并将其插入已完成列表。因为它不是特定于并发的,所以我忽略了 SQL 命令(完成一项工作需要几十分钟到几个小时。)

这对我来说是一个令人讨厌的惊喜,两个进程运行上面完全相同的查询(几乎同时请求作业)可能获得相同的作业 ID (fid)。我提出的唯一可能的解释是 Postgres 不依赖于 ACID 规范。注释?

附加信息:我将事务设置为可序列化(在postgresql.conf set default_transaction_isolation = 'serializable'中)。现在,DBMS 会在隔离未完成的情况下使事务失败。是否可以强制 Postgres 自动重启它们?

postgresql postgresql-9.1
  • 2 2 个回答
  • 759 Views

2 个回答

  • Voted
  1. Best Answer
    Craig Ringer
    2014-10-22T15:32:50+08:002014-10-22T15:32:50+08:00

    查询的具体问题

    PostgreSQL 默认为READ COMMITTED隔离,看起来你没有使用任何不同的东西。在READ COMMITTED每个语句中获取自己的快照。它看不到来自其他事务的未提交的更改;就好像它们根本就没有发生过一样。

    现在,假设您在三个会话中同时运行此程序,设置中包含三个条目 in jobs、 zero injobs_running和 zero in jobs_completed:

    insert into piper.jobs_running
    select x.fid from ( 
      SELECT fid FROM piper.jobs
      except 
      select fid from piper.jobs_running
      except 
      select fid from piper.jobs_completed
     ) as x limit 1
    returning(fid)
    

    每次运行都会选择jobs. 因为他们的快照都是在他们中的任何一个提交更改之前拍摄的,甚至是在创建未提交的行之前拍摄的,*他们都会在jobs_running和中找到零行jobs_completed。

    所以他们都要求一份工作。可能是同一份工作,因为即使没有ORDER BY,扫描顺序也是一样的。

    锁定

    行锁定跨越跨国边界,让您可以在事务之间进行通信以强制执行排序。所以你可能认为这会解决你的问题,但它不会。

    如果您FOR UPDATE在row条目上锁定,则该行被独占锁定,锁定将一直保持到事务提交或回滚。所以你会认为下一个事务会得到不同的行,或者如果它试图得到相同的行,它会等待锁释放,看到 中现在有一个条目jobs_running,然后跳过该行。你错了。

    将会发生的是您将锁定所有行。一个事务将成功获取所有行的锁。将执行相同索引或顺序扫描的其他事务通常会尝试以相同顺序锁定行,在第一次锁定尝试时卡住,并等待第一个事务回滚或提交。如果你不走运,它们可能会开始锁定不同的行集并相互死锁,从而导致死锁中止,​​但通常你只是没有得到有用的并发。

    更糟糕的是,第一个事务选择它锁定的行之一,插入一行jobs_running并提交,释放锁。然后另一个事务能够继续,并锁定所有行....但它没有获得数据库状态的新快照(快照在语句开始时拍摄),所以*它看不到你插入一排成jobs_running。因此,它获取相同的作业,将该作业的一行插入到jobs_running中,然后提交。

    条件重新检查

    PostgreSQL 有一个大多数数据库都没有的古怪功能,如果一个事务阻塞在锁上,它会在第一个事务提交后获得锁时重新检查所选行是否仍然匹配锁条件。

    这就是https://stackoverflow.com/questions/11532550/atomic-update-select-in-postgres中的示例起作用的原因 - 它依赖于回收锁后的限定符重新检查WHERE子句。

    锁定的使用强制所有事情串行运行,所以在实践中你还不如让一个连接来完成工作。

    隔离、ACID 和现实

    PostgreSQL 中的事务隔离并不是事务并发运行的完美理想,但它们的效果与串行执行时的效果相同。

    唯一能够提供完美隔离的真实世界数据库是在写入事务首次访问每个表时专门锁定每个表的数据库,因此在实践中,如果事务针对数据库的不同部分,则只能是并发访问。没有人愿意在需要或有用并发的情况下使用这样的数据库。

    所有现实世界的实施都是妥协。

    READ COMMITTED默认

    PostgreSQL 的默认设置是READ COMMITTED隔离,这是一个定义明确的隔离级别,允许不可重复和幻读,如PostgreSQL 事务隔离手册中所述。

    SERIALIZABLE隔离

    您可以在每个事务的基础上或作为每个用户、每个数据库或(不推荐)全局默认值请求更严格的SERIALIZABLE隔离。这提供了更强大的保证,但仍然不完美,如果它们以串行运行时不会发生的方式交互,则以强行回滚事务为代价。

    因为您的并发查询将始终尝试获取第一份工作,所以无论有多少工作,除了一个以外的所有工作都会因序列化失败而中止。因此在实践中,您不会获得任何有用的并发性,还不如使用单个连接将作业分发给工作人员。

    (请注意,在 PostgreSQL 9.1 之前SERIALIZABLE提供的保证要弱得多,并且不会检测到很多事务相互依赖的情况。)

    自动重新运行SERIALIZABLE

    PostgreSQL 不会自动重新运行SERIALIZABLE因序列化失败而中止的事务。在某些情况下这会非常有用,但在其他情况下这样做将是完全错误和危险的——尤其是在涉及通过应用程序进行读/修改/写周期的情况下。目前不支持在序列化失败时自动重新运行事务。应用程序应重试。

    不要自己动手写排队系统

    看起来你正在尝试做的是编写一个排队系统。考虑不这样做。编写一个健壮、可靠和正确的排队系统是很困难的,并且有一些已经可用的好系统可供您采用。您必须处理诸如碰撞安全性、当有人接受任务但未能完成任务时会发生什么、完成时的竞争条件就像您放弃完成它的任务处理程序等。有很多微妙的并发问题。不要尝试自己动手做。

    9.5 和SKIP LOCKED

    仍在开发中的 PostgreSQL 9.5 添加了一项功能,使排队变得更加容易。

    它让你说如果当你SELECT ... FOR UPDATE,如果一行被锁定,你应该忽略它并继续寻找下一个未锁定的行。这在与 a 结合时非常有用,LIMIT因为它可以说“找到其他人尚未尝试声明的第一行”。因此,编写安全并发地抓取作业的队列变得非常简单。

    在该功能可用之前,我强烈建议您坚持使用单连接队列管理,或者使用经过良好测试的任务队列系统。

    • 6
  2. arthur
    2014-10-24T02:48:14+08:002014-10-24T02:48:14+08:00

    craig-ringer 的回答完全回答了这个问题。为了完整起见,我添加了以下简单代码来解决使用锁的问题:

    create or replace function f_requestJob(
        jobs           text, 
        jobs_running   text, 
        jobs_completed text
    ) 
        returns bigint as
    $$
    declare 
        sql varchar;
        c bigint;
    
    begin
        execute 'LOCK TABLE ' || jobs           || ' IN ACCESS EXCLUSIVE MODE'; 
        execute 'LOCK TABLE ' || jobs_running   || ' IN ACCESS EXCLUSIVE MODE'; 
        execute 'LOCK TABLE ' || jobs_completed || ' IN ACCESS EXCLUSIVE MODE';
    
        sql := '
        insert into ' || jobs_running || '
        select x.fid     
        from ( 
          SELECT fid FROM ' || jobs || '
          except 
          select fid from ' || jobs_running || '
          except 
          select fid from ' || jobs_completed || '
         ) as x limit 1
        returning(fid)';
    
        execute sql into c;
    
        return c; --may be a null value
    
    end;
    $$ language plpgsql;
    
    • 0

相关问题

  • 我可以在使用数据库后激活 PITR 吗?

  • 运行时间偏移延迟复制的最佳实践

  • 存储过程可以防止 SQL 注入吗?

  • PostgreSQL 中 UniProt 的生物序列

  • PostgreSQL 9.0 Replication 和 Slony-I 有什么区别?

Sidebar

Stats

  • 问题 205573
  • 回答 270741
  • 最佳答案 135370
  • 用户 68524
  • 热门
  • 回答
  • Marko Smith

    连接到 PostgreSQL 服务器:致命:主机没有 pg_hba.conf 条目

    • 12 个回答
  • Marko Smith

    如何让sqlplus的输出出现在一行中?

    • 3 个回答
  • Marko Smith

    选择具有最大日期或最晚日期的日期

    • 3 个回答
  • Marko Smith

    如何列出 PostgreSQL 中的所有模式?

    • 4 个回答
  • Marko Smith

    列出指定表的所有列

    • 5 个回答
  • Marko Smith

    如何在不修改我自己的 tnsnames.ora 的情况下使用 sqlplus 连接到位于另一台主机上的 Oracle 数据库

    • 4 个回答
  • Marko Smith

    你如何mysqldump特定的表?

    • 4 个回答
  • Marko Smith

    使用 psql 列出数据库权限

    • 10 个回答
  • Marko Smith

    如何从 PostgreSQL 中的选择查询中将值插入表中?

    • 4 个回答
  • Marko Smith

    如何使用 psql 列出所有数据库和表?

    • 7 个回答
  • Martin Hope
    Jin 连接到 PostgreSQL 服务器:致命:主机没有 pg_hba.conf 条目 2014-12-02 02:54:58 +0800 CST
  • Martin Hope
    Stéphane 如何列出 PostgreSQL 中的所有模式? 2013-04-16 11:19:16 +0800 CST
  • Martin Hope
    Mike Walsh 为什么事务日志不断增长或空间不足? 2012-12-05 18:11:22 +0800 CST
  • Martin Hope
    Stephane Rolland 列出指定表的所有列 2012-08-14 04:44:44 +0800 CST
  • Martin Hope
    haxney MySQL 能否合理地对数十亿行执行查询? 2012-07-03 11:36:13 +0800 CST
  • Martin Hope
    qazwsx 如何监控大型 .sql 文件的导入进度? 2012-05-03 08:54:41 +0800 CST
  • Martin Hope
    markdorison 你如何mysqldump特定的表? 2011-12-17 12:39:37 +0800 CST
  • Martin Hope
    Jonas 如何使用 psql 对 SQL 查询进行计时? 2011-06-04 02:22:54 +0800 CST
  • Martin Hope
    Jonas 如何从 PostgreSQL 中的选择查询中将值插入表中? 2011-05-28 00:33:05 +0800 CST
  • Martin Hope
    Jonas 如何使用 psql 列出所有数据库和表? 2011-02-18 00:45:49 +0800 CST

热门标签

sql-server mysql postgresql sql-server-2014 sql-server-2016 oracle sql-server-2008 database-design query-performance sql-server-2017

Explore

  • 主页
  • 问题
    • 最新
    • 热门
  • 标签
  • 帮助

Footer

AskOverflow.Dev

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve