我有一个由一列预先填充的数字组成的表。我使用Nhibernate的API抓取前10行,其中'Used'标志设置为false。 当多个会话试图从表中获取行时,避免并发问题的最佳方法是什么? 选择行后,我可以将标志列更新为True,以便后续调用不会使用相同的数字。
答案 0 :(得分:0)
有了这样一般的背景,可以这样做:
// RepeatableRead ensures the read rows does not get concurrently updated by another
// session.
using (var tran = session.BeginTransaction(IsolationLevel.RepeatableRead))
{
var entities = session.Query<Entity>()
.Where(e => !e.Used)
.OrderBy(e => e.Id)
.Take(10)
.ToList();
foreach(var entity in entities)
{
e.Used = true;
}
// If your session flush mode is not the default one and does not cause
// commits to flush the session, add a session.Flush(); call before committing.
tran.Commit();
return entities;
}
很简单。它可能会因死锁而失败,在这种情况下,您必须丢弃会话,获取新会话,然后重试。
使用乐观更新模式可能是一种替代解决方案,但这也需要一些代码来从失败的尝试中恢复。
使用不会导致死锁风险的无显式锁定解决方案可以做到这一点,但需要更多查询:
const int entitiesToObtain = 10;
// Could initialize here with null instead, but then, will have to check
// for null after the while too.
var obtainedEntities = new List<Entity>();
while (obtainedEntities.Count == 0)
{
List<Entity> candidates;
using (var tran = session.BeginTransaction())
{
candidatesIds = session.Query<Entity>()
.Where(e => !e.Used)
.Select(e => e.Id)
.OrderBy(id => id)
.Take(entitiesToObtain)
.ToArray();
}
if (candidatesIds.Count == 0)
// No available entities.
break;
using (var tran = session.BeginTransaction())
{
var updatedCount = session.CreateQuery(
@"update Entity e set e.Used = true
where e.Used = false
and e.Id in (:ids)")
.SetParameterList("ids", candidatesIds)
.ExecuteUpdate();
if (updatedCount == candidatesIds.Length)
{
// All good, get them.
obtainedEntities = session.Query<Entity>()
.Where(e => candidatesIds.Contains(e.Id))
.ToList();
tran.Commit();
}
else
{
// Some or all of them were no more available, and there
// are no reliable way to know which ones, so just try again.
tran.Rollback();
}
}
}
根据建议使用NHibernate DML-style operations here。 NHibernate v5.0中提供了strongly typed alternative。