我认为这是一个常见的问题,但我还没有找到任何解决方案,也许我并没有在谷歌中正确地搜索问题。总之,我有一个在表中插入多行的进程(在同一事务中的许多其他内容中),但该进程在多个线程和多个服务器中执行。
TABLE: COVERAGES
COLUMNS: COV_Id, COV_Description
描述是唯一的,但不是数据库(遗留)中的约束,我想避免插入重复的描述。我已经隔离了搜索并插入了一个独立的事务中,我想在选择之前锁定表格并在" Save"之后解放它。如果它不存在。
我会喜欢(高级别):
{
this.Lock(Coverage); // Lock table Coverages to avoid select out of this transaction
Coverage coverage = session.QueryOver<Coverage>().Where(g => g.Description == description).Take(1).SingleOrDefault();
if (coverage == null)
{
this.Save(new Coverage { Description = description });
}
return coverage;
};
我不能使用C#的锁定指令,因为这个过程是在多个服务器上执行的,我不能使用NHibernate的Lock指令,因为当我没有结果时,我想要阻止
我使用NHibernate 3.3 for SqlServer和Oracle。
答案 0 :(得分:2)
我终于在数据库上实现了一个信号量来解决问题。正如我在上面与Frédéric的“讨论”中提到的那样,我需要将线程锁定在select以避免重复插入,Serializable隔离级别,锁定INSERT并在SQL Server上的并发调用中调用insert时抛出死锁异常。通过Oracle的其他方式抛出错误08177. 00000 - “无法序列化此事务的访问”,或者等待另一个事务的结束插入稍后重复的值(请参阅下面的示例sql)。
所以解决方案是这样的:
public Coverage CreateCoverageSessionIsolated(string description, out bool isNew)
{
Coverage coverage = null;
bool _isNew = false;
this.ExecuteOnNewSession((session) =>
{
this.semphoresDao.LockSemaphore(session, "SMF_COVERAGES");
coverage = session.QueryOver<Coverage>()
.Where(g => g.Description == description)
.Take(1)
.SingleOrDefault();
_isNew = coverage == null;
if (coverage == null)
{
coverage = new Coverage { Description = description };
this.Save(coverage);
}
});
isNew = _isNew;
return coverage;
}
我对实际代码进行了一些调整,以便更好地进行扩展。
我已经尝试过,并且在SQL Server和Oracle上运行良好。
修改强> 要检查Serializable事务的解决方案是否适合我,我会在两个并行执行的并发事务上使用该简单的SQL代码:
BEGIN TRAN; -- ONLY FOR SQL
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
SELECT COV_ID FROM COVERAGES WHERE COV_DESCRIPTION = 'testcov';
INSERT INTO COVERAGES (COV_DESCRIPTION) VALUES ('testcov');
COMMIT;
答案 1 :(得分:1)
您可以使用IsolationLevel.Serializable
锁定您的桌子。
using (var t = session.BeginTransaction(IsolationLevel.Serializable))
{
var coverage = session.QueryOver<Coverage>()
.Where(g => g.Description == description)
.Take(1).SingleOrDefault();
if (coverage == null)
{
coverage = new Coverage { Description = description };
session.Save(coverage);
}
t.Commit();
return coverage;
}
为了限制锁争用,这要求您的表在Description
上有一个索引,并且该索引实际上是由读取查询使用的。否则,它将锁定整个表,而不是仅锁定“附近”Description
值。阅读更多here。对于比.Net Framework 1更完整的官方文档,请阅读here和here。
如果两个或多个进程(或线程)尝试执行并发冲突的插入 1 ,则它们都会遇到死锁。所有这些除了一个将被作为死锁受害者回滚。剩下的一个将继续。
插入时发生死锁,而不是选择。所有进程都会挂在插件上,所有进程都会结束回滚,除外。这样可以确保不会插入重复项。
这意味着完整的处理代码更加精细。
while (true)
{
using (var session = sessFactory.OpenSession())
{
try
{
using (var t = session.BeginTransaction(IsolationLevel.Serializable))
{
var coverage = session.QueryOver<Coverage>()
.Where(g => g.Description == description)
.Take(1).SingleOrDefault();
if (coverage == null)
{
coverage = new Coverage { Description = description };
session.Save(coverage);
}
t.Commit();
// Breaks the loop by the way.
return coverage;
}
}
catch (GenericADOException ex)
{
// SQL-Server specific code for identifying deadlocks
var sqlEx = ex.InnerException as SqlException;
if (sqlEx == null || sqlEx.Number != 1205)
throw;
// Deadlock, just try again by letting the loop go on (eventually
// log it).
}
}
}
注意:强>
1.根据DB锁定的范围发生冲突,不仅仅是要插入的实际值。强烈建议使用足够的指数来缩小此范围。没有一个可能导致整个表被锁定,导致同时插入不同值的能力非常差。