Yur*_*rin 5 postgresql isolation-level serialization upsert
可序列化隔离模式可用于避免更新插入相等 id 时的竞争条件。因此,create table u(uid int primary key, name text);
如果我们运行两个相似的事务 T1 和 T2:
begin isolation level serializable;
select * from u where uid = 1;
Run Code Online (Sandbox Code Playgroud)
然后继续 T1 和 T2:
insert into u (uid, name) values (1, 'A');
Run Code Online (Sandbox Code Playgroud)
commit;
只有第一个成功,而另一个抛出序列化失败之后。
这是该模式的一个非常巧妙的功能,可以处理复杂交易中的独特密钥违规,而不是诉诸特定的“黑客” insert ... on conflict
。然而,即使 uid 不同,例如uid = 2
和uid = 3
,事务 T1 和 T2仍然无法提交。
怎么可能?据说他们创建了不同的谓词 SIReadlocks 并select
使用索引扫描。窍门在哪里?
原因是桌子完全是空的。
_bt_first
请参阅中的以下代码src/backend/access/nbtree/nbtsearch.c
:
if (!BufferIsValid(buf))
{
/*
* We only get here if the index is completely empty. Lock relation
* because nothing finer to lock exists.
*/
PredicateLockRelation(rel, scan->xs_snapshot);
[...]
return false;
}
else
PredicateLockPage(rel, BufferGetBlockNumber(buf),
scan->xs_snapshot);
Run Code Online (Sandbox Code Playgroud)
通常,索引扫描会SIRead
在索引条目所在的索引页上加锁,但由于索引为空,PostgreSQL 会SIRead
在整个表上加锁。
现在,由于两个事务都这样做,因此您会在其中一个事务结束时收到序列化错误,因为写入与表范围的读锁发生冲突。
如果表不为空,您会注意到像这样的并发事务有时会成功,因为它们影响不同的索引页。如果受影响的uid
s 足够接近,以至于它们位于同一索引页上,您仍然会遇到“误报”序列化错误。这是有记录的:
虽然 PostgreSQL 的可序列化事务隔离级别仅在可以证明存在可产生相同效果的串行执行顺序的情况下才允许并发事务提交,但它并不总是能够防止引发真正串行执行中不会发生的错误。特别是,即使在尝试插入密钥之前显式检查密钥不存在之后,也可能会看到由于与重叠的可序列化事务冲突而导致的唯一约束违规。
SIReadLock
要查看PostgreSQL 使用哪种类型,请检查pg_locks
.
归档时间: |
|
查看次数: |
970 次 |
最近记录: |