【问题标题】:Postgresql Serializable Transaction not working as expectedPostgresql 可序列化事务未按预期工作
【发布时间】:2018-10-05 19:40:43
【问题描述】:

我想实现的任务分配 em>的系统。用户可以从池中请求任务。即使设置为序列,交易有时会给出相同的任务给多个用户,即使它不应该。 P>

简化架构: H3>
CREATE TABLE tasks(
  _id CHAR(24) PRIMARY KEY,
  totalInstances BIGINT NOT NULL
);

CREATE TABLE assigned(
  _id CHAR(24) PRIMARY KEY,
  _task CHAR(24) NOT NULL
);

在工作表中充满了许多行,让我们说每个人都有totalInstances = 1,这意味着每个任务应该最多分配一次。 P>

查询中添加一行assigned: H3>
WITH task_instances AS (
  SELECT t._id, t.totalInstances - COUNT(assigned._id) openInstances
  FROM tasks t
  LEFT JOIN assigned ON t._id = assigned._task
  GROUP BY t._id, t.totalInstances
),

selected_task AS (
  SELECT _id
  FROM task_instances
  WHERE openInstances > 0
  LIMIT 1
)

INSERT INTO assigned(_id, _task)
SELECT $1, _id
FROM selected_task;

$1被传递到每个查询的随机ID。 P>

症状 H3>

我们有大约100个活跃用户reqularly请求的任务。该作品如预期,在1000个请求可能除了一次。 然后,两个assigned相同 EM> _task时并行请求ID创建的行。我期望可序列化的执行回滚第二个,由于openInstances本来应该通过的第一个下降到0。 P>

设置

我们使用的Postgres 10.3和该查询从Scala代码经由油滑3.2.3 withTransactionIsolation(Serializable)运行。没有其他查询从删除或插入assigned表。 P>

Postgres的日志显示请求被在不同的会话中运行,并且SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL SERIALIZABLE;是每个任务分配查询之前执行。 P>

我试图重写在不同风格的查询,inculding VIEW S的使用量为WITH子查询,以及围绕与BEGINCOMMIT但没有效果。 P>的查询

感谢任何帮助。

编辑

我要补充一点,有时预期的序列错误/回滚的不 em>的出现,在我们的应用程序重试查询。我看到这个正确的行为中的最后一个小时日志10倍,但2次它仍然错误地分配相同的任务两次,如上所述。 P>

【问题讨论】:

    标签: sql postgresql concurrency transactions serializable


    【解决方案1】:

    Serializable 隔离级别并不意味着事务实际上是串行的。它只保证读取已提交、可重复读取和不存在幻读。而且您所描述的行为看起来并不违法。

    为了避免重复记录,你可以简单地做

    select ... from task_instances for update
    

    由于这个“for update”子句,选定的行将在事务生命周期内被锁定。所以只有一个事务能够更新,第二个事务必须等到第一个事务被提交。结果,第二个事务将读取第一个事务更新的值 - 这正是您需要的保证。

    同样重要的是,如果你在这种情况下使用“select for update”,你甚至不需要 Serializable 隔离级别,读提交就足够了。

    【讨论】:

    • 我认为你是对的。更新同一行时会引发序列化错误,而不是执行插入或选择时。
    • @JustMe,什么是序列化错误?通常,另一个数据库客户端会被阻塞,直到使用“for update”提交事务。
    • 是的,你是对的。我的意思是“由于并发更新而无法序列化访问”,例如 blog.2ndquadrant.com/… SERIALIZABLE 事务主题。
    • @user3714601,这只会锁定选定的行,对吗?我认为这没有帮助,因为查询不会更新任何行,并且仍然可以进行并发插入。
    • 我认为这里的重点是避免锁定。此外,可串行化不仅仅意味着没有幻读,还意味着必须有一个等效的空中执行顺序(实际上没有)。简而言之,我认为查询应该按预期工作。我去看看。
    【解决方案2】:

    我试过你的例子是这样的:

    第 1 节:

    BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;
    
    WITH task_instances AS (
      SELECT t._id, t.totalInstances - COUNT(assigned._id) openInstances
      FROM tasks t
      LEFT JOIN assigned ON t._id = assigned._task
      GROUP BY t._id, t.totalInstances
    ),
    selected_task AS (
      SELECT _id
      FROM task_instances
      WHERE openInstances > 0
      LIMIT 1
    )
    INSERT INTO assigned(_id, _task)
    SELECT 1, _id
    FROM selected_task;
    

    第 2 节:

    BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;
    
    WITH task_instances AS (
      SELECT t._id, t.totalInstances - COUNT(assigned._id) openInstances
      FROM tasks t
      LEFT JOIN assigned ON t._id = assigned._task
      GROUP BY t._id, t.totalInstances
    ),
    selected_task AS (
      SELECT _id
      FROM task_instances
      WHERE openInstances > 0
      LIMIT 1
    )
    INSERT INTO assigned(_id, _task)
    SELECT 2, _id
    FROM selected_task;
    
    COMMIT;
    

    第 1 节:

    COMMIT;
    

    这就是我得到的:

    ERROR:  could not serialize access due to read/write dependencies among transactions
    DETAIL:  Reason code: Canceled on identification as a pivot, during commit attempt.
    HINT:  The transaction might succeed if retried.
    

    所以它按预期工作。

    我的唯一解释是你的设置有问题,而且你毕竟没有使用SERIALIZABLE

    您是否曾在应用程序中看到序列化错误?如果不是,那将证实我的怀疑。

    【讨论】:

    • 我们确实看到这样的序列化错误,我们的应用程序会在这些错误上重试查询。我在过去几个小时的日志中看到了这种正确的行为 10 次,但有 2 次它仍然错误地分配了两次相同的任务……
    • 那么我唯一的解释是 a) 应用程序逻辑中的错误(并非总是使用 SERIALIZABLE)b) 其他事务正在插入这些行 c) PostgreSQL 中存在错误。第三个选项当然是可能的,但我会先调查其他两个。
    • a) 我们调查了并行分配,日志在相应会话中的两个事务之前直接显示SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL SERIALIZABLE;b) 没有其他东西插入这些行,我们仔细检查了这一点。 c) 我们首先在 10.2 中遇到了这些问题,然后升级到 10.3,修复了 misbehavior of concurrent-update rechecks with CTE references appearing in subplans (changelog)。升级未解决我们的问题,我们不确定此更改是否相关。
    猜你喜欢
    • 2021-05-11
    • 1970-01-01
    • 2018-05-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-01-18
    相关资源
    最近更新 更多