Postgres SSI 行为

Postgres SSI Behavior

我正在尝试了解 SSI 在 Postgres 中的实际行为。我的理解是,如果我有两个事务与同一个 table 进行交互,但事务不与 table 中的相同行进行交互,则不会发生异常。

但是,我运行正在执行以下测试,其中事务一执行以下操作:

cur = engine.cursor()
cur.execute('SELECT SUM(value) FROM mytab WHERE class = 1')
s = cur.fetchall()[0][0]
print('retrieved sum is...')
print(s)
print('sleeping....')
time.sleep(10)
cur.execute('INSERT INTO mytab (class, value) VALUES (%s, %s)', (1, s))
engine.commit()

虽然上面的第一笔交易正在休眠,但我 运行 第二笔交易:

cur = engine.cursor()
cur.execute('SELECT SUM(value) FROM mytab WHERE class = 2')
s = cur.fetchall()[0][0]
print('retrieved sum is...')
print(s)
cur.execute('INSERT INTO mytab (class, value) VALUES (%s, %s)', (2, s))
engine.commit()

在这种情况下,第二个事务仅涉及 class = 2 的行,而第一个事务仅涉及 class = 1 的行。但这导致第一个事务失败但有以下例外:

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

供参考 mytab 很简单,看起来像这样:

class   value
1   10
1   20
2   100
2   200

除了标准 engine = psycopg2.connect 设置之外,我还在 运行 上述代码之前使用此行设置事务隔离级别:

engine.set_isolation_level(psycopg2.extensions.ISOLATION_LEVEL_SERIALIZABLE)

你的理解基本正确,但是SSI算法并不完美,所以总会有一些误报的风险(比如docs中提到的,行锁可能会组合成一个页面锁定,以精度为代价优化内存)。

此处的行为是 predicate locking implementation 的限制,即:

For a table scan, the entire relation will be locked.

基本上,在您的第一个查询 WHERE class = 1 已经 运行 之后,需要检查来自其他事务的未来插入以查看它们是否 满足如果他们是可见的,这种情况。实际上,除了最简单的条件外,执行此检查对于所有情况都是不切实际或不可能的,因此为了谨慎起见,整个 table 取而代之的是谓词锁。

细粒度谓词锁实现是 based on indexing,因为根据例如B 树范围比任意 WHERE 约束条件。

换句话说,如果您在 class 列上有一个索引 - 并且在您的 table 中有足够的记录供计划者实际使用它 - 您应该会得到您期望的行为。