Postgresql Serializable Transaction无法按预期工作

fm0*_*fm0 7 sql postgresql concurrency transactions serializable

我正在尝试实现一个任务分配系统.用户可以从池中请求任务.即使设置为SERIALIZABLE,事务有时也会向多个用户提供相同的任务,即使它不应该.

简化架构:

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
);
Run Code Online (Sandbox Code Playgroud)

任务表中填充了许多行,假设每个行都有totalInstances = 1,这意味着每个任务最多应分配一次.

查询以添加行assigned:

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;
Run Code Online (Sandbox Code Playgroud)

$1传递给每个查询的随机ID.

症状

我们有大约100名活跃用户请求任务.这可以按预期工作,除了1000个请求中的一次.然后,在并行请求时assigned同一 _task id 创建两行.我希望可序列化的执行回滚第二个,因为openInstances应该被第一个减少到0.

建立

我们使用Postgres 10.3,查询是通过Slick 3.2.3从Scala代码运行的withTransactionIsolation(Serializable).没有其他查询从assigned表中删除或插入.

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

我试图重写不同风格的查询,inculding的使用VIEWS为WITH子查询,并与周围的查询BEGINCOMMIT,但没有效果.

任何帮助表示赞赏.

编辑

我要补充一点,有时预期的序列错误/回滚上来,在我们的应用程序试查询.我在最后几小时的日志中看到了这种正确的行为10次,但是仍然错误地将其分配了两次相同的任务,如上所述.

use*_*601 5

可串行化隔离级别并不意味着事务实际上是串行的。它仅保证读已提交、可重复读和不存在幻读。而且您所描述的行为并不构成违规。

为了避免重复记录,您可以简单地执行以下操作

select ... from task_instances for update
Run Code Online (Sandbox Code Playgroud)

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

同样重要的是,如果在这种情况下使用“选择更新”,则甚至不需要可序列化的隔离级别,已提交的读就足够了。