TL; DR:下面的问题归结为:插入行时,在生成新Identity
值和锁定聚集索引中的相应行键之间是否存在机会之窗,外部观察者可以看到更新的值 Identity
并发交易插入的价值?(在SQL Server中。)
详细版本
我有一个带有Identity
名为的列的SQL Server表CheckpointSequence
,这是表的聚集索引(也具有许多其他非聚集索引)的键。通过多个并发进程和线程将行插入表中(处于隔离级别READ COMMITTED
,并且没有IDENTITY_INSERT
)。同时,有些进程会定期从聚簇索引中读取行,并按该CheckpointSequence
列排序(也处于隔离级别READ COMMITTED
,并且该READ COMMITTED SNAPSHOT
选项处于关闭状态)。
我目前依靠这样的事实,即读取过程永远不会“跳过”检查点。我的问题是:我可以依靠这个财产吗?如果没有,我该怎么做才能实现?
示例:当插入具有标识值1、2、3、4和5 的行时,阅读器在看到具有值4的行之前必须看不到具有值5的行。测试表明该查询包含一个ORDER BY CheckpointSequence
子句(和一个WHERE CheckpointSequence > -1
子句),只要要读取第4行但尚未提交,就可靠地阻塞,即使第5行已经提交。
我认为至少从理论上讲,这里可能存在一种竞争条件,可能会导致这一假设被打破。不幸的是,关于多个并发事务的工作方式的文档Identity
并没有太多说明Identity
,而只是说“每个新值都是基于当前的种子和增量生成的”。和“特定交易的每个新值都与表上的其他并发交易不同”。(MSDN)
我的推理是,它必须以这种方式工作:
- 事务开始(显式或隐式)。
- 生成标识值(X)。
- 根据身份值在聚集索引上获取相应的行锁(除非发生锁升级,在这种情况下整个表都被锁定)。
- 该行已插入。
- 事务已提交(可能要花很多时间),因此将再次删除该锁。
我认为在第2步和第3步之间,有一个很小的窗口,
- 并发会话可以生成下一个标识值(X + 1)并执行所有其余步骤,
- 因此,允许阅读者恰好在该时间点阅读值X + 1,而忽略了X的值。
当然,这种可能性似乎很小。但仍然-可能会发生。可以吗
(如果您对上下文感兴趣:这是NEventStore的SQL Persistence Engine的实现。NEventStore实现一个仅附加事件存储,其中每个事件都获得一个新的,升序的检查点序列号。客户端从按检查点排序的事件存储中读取事件为了执行各种计算,一旦处理了带有检查点X的事件,客户就只考虑“较新的”事件,即带有检查点X + 1及更高版本的事件,因此,至关重要的是永远不能跳过事件,因为它们再也不会被考虑了,我目前正在尝试确定Identity
基于-checkpoint的实现是否满足此要求。这些是所使用的确切SQL语句:Schema,Writer的query,读者查询。)
如果我是对的,并且可能出现上述情况,那么我只能看到两种处理方式,但都不令人满意:
- 在看到X之前看到检查点序列值X + 1时,关闭X + 1并稍后再试。但是,由于
Identity
当然会产生间隙(例如,当事务回滚时),因此X可能永远不会出现。 - 因此,相同的方法,但是在n毫秒后接受间隔。但是,我应该假定n的值是多少?
还有更好的主意吗?