2

我试图了解 SSI 在 Postgres 中的实际行为方式。我的理解是,如果我有两个事务与同一个表交互,但事务没有与表中的相同行交互,那么不会发生异常。

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

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)
4

1 回答 1

2

您的理解是非常正确的,但是 SSI 算法并不完美,因此总是存在一些误报的风险(例如,如文档中所述,行锁可能合并为页锁,以优化内存为代价精度)。

这里的行为是谓词锁定实现的限制,即:

对于表扫描,整个关系将被锁定。

基本上,在您的第一个查询WHERE class = 1运行后,需要检查来自其他事务的未来插入,以查看如果它们可见,它们是否满足此条件。除了最简单的情况外,实际上执行此检查是不切实际或不可能的,因此为了谨慎起见,改为在整个表上使用谓词锁。

细粒度的谓词锁实现是基于索引WHERE的,因为根据例如 B 树范围比根据任意约束来推断受影响的关系子集要容易得多。

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

于 2019-09-05T01:23:46.787 回答