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的使用VIEW
S为WITH
子查询,并与周围的查询BEGIN
和COMMIT
,但没有效果.
任何帮助表示赞赏.
我要补充一点,有时预期的序列错误/回滚不上来,在我们的应用程序试查询.我在最后几小时的日志中看到了这种正确的行为10次,但是仍然错误地将其分配了两次相同的任务,如上所述.
可串行化隔离级别并不意味着事务实际上是串行的。它仅保证读已提交、可重复读和不存在幻读。而且您所描述的行为并不构成违规。
为了避免重复记录,您可以简单地执行以下操作
select ... from task_instances for update
Run Code Online (Sandbox Code Playgroud)
由于这个“for update”子句,选定的行将在事务生命周期内被锁定。因此,只有一个事务能够更新,第二个事务必须等待第一个事务提交。因此,第二个事务将读取第一个事务更新的值 - 这正是您在这里需要的保证。
同样重要的是,如果在这种情况下使用“选择更新”,则甚至不需要可序列化的隔离级别,已提交的读就足够了。
归档时间: |
|
查看次数: |
535 次 |
最近记录: |