背景
Alfresco使用默认的数据库事务隔离,对于我们的Oracle数据库是READ_COMMITED。我正在开发一个项目,其中不可重复和幻像读取可能是一个主要问题,所以我正在研究使用SERIALIZABLE事务隔离来避免这种情况。
一方面,我们将拥有一个自定义API服务,它将更改分组为原子事务 - 基本上是对文档的CRUD操作。 另一方面,我们将有后台进程更新并行运行的这些文档的元数据。
这些操作将使用事务元数据查询,因为最终将一致的SOLR查询添加到组合中不会使问题进一步复杂化。
目标是在API服务运行时能够进行主要的元数据模型迁移。出于这个问题的目的,我将使用一个属性作为示例,但IRL会有很多这种变化。例如,我们目前有一个带有约束的元数据字段:mymodel:doctypes1。但我们需要将mymodel:doctypes1中的值重新映射到具有不同约束的新字段:mymodel:doctypes2。 (不要问我为什么,我无法控制这个决定,我个人质疑这种改变的智慧)。
我对READ_COMMITTED隔离的理解告诉我,在这种情况下,我们非常容易受到以下情况的影响:
这两个值现在不一致:我认为这个错误被称为不可重复的读取。
问题
将Oracle数据库设置为SERIALIZABLE会阻止此问题吗? Alfresco正在使用Spring交易。我的理解是,使用Spring事务的可序列化tx隔离可以防止这个问题“透明地”发生。
有没有人有任何将Alfresco数据库设置为SERIALIZABLE的实际经验?你试图解决类似的问题吗?它有用吗?它对你有什么样的性能影响?
非常感谢您分享您的经历!
答案 0 :(得分:2)
在Alfresco论坛上的Axel Faust通过指出RetryingTransactionHelper强制执行乐观锁定并在两个事务重叠时重试来帮助我解决这个问题。如果你对此感到好奇,我会推荐他的帖子。
只是为了确保我做了一个小模型来激发ConcurrencyException并检查它确实表现得正确。
我使用主类将同一节点的49个更新发送到线程池:
for (int i=0; i < 50; i++) {
TestIncrementer ti = new TestIncrementer(node);
threadPoolExecuter.submit(ti);
}
我的增量器只是读取现有的标题,随机睡觉,然后添加到它上面。
int hash = RetryingTransactionHelper.getActiveUserTransaction().hashCode();
log.debug("Thread id: " + Thread.currentThread().getId() + " tx " + hash);
String title = (String) nodeService.getProperty(nodeRef, ContentModel.PROP_TITLE);
Thread.sleep(Math.round(Math.random()* 1000));
nodeService.setProperty(nodeRef, ContentModel.PROP_TITLE, title + "1");
log.debug("Title: " + title);
正如所料,我看到了并发异常和重试:
Transaction commit failed:
Thread: defaultAsyncAction4
Txn: UserTransaction[object=org.alfresco.util.transaction.SpringAwareUserTransaction@65b249f2, status=0]
Iteration: 8
Exception follows:
org.springframework.dao.ConcurrencyFailureException: Failed to update node 126094
但他们正在重审。
最终的重试让节点只有49个聚合!这正是需要发生的事情。
结论是,如果将RetryingTransactionHelper用于后台进程,则更改数据库隔离绝对不是必需的,甚至也不可取。