我认为这是一个常见问题,但我还没有找到任何解决方案,也许我没有在谷歌中正确搜索问题。总而言之,我有一个在表中插入多行的过程(在同一事务中的许多其他事物中),但该过程是在多个线程和多个服务器中执行的。
TABLE: COVERAGES
COLUMNS: COV_Id, COV_Description
描述是唯一的,但不是数据库中的约束(旧版),我想避免插入重复的描述。我已经将搜索和插入隔离在一个独立的事务中,我想在选择之前锁定表并在“保存”之后释放它(如果它不存在)。
我想要那样的东西(高级):
{
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#的lock指令,因为进程是在多个服务器上执行的,我不能使用NHibernate的Lock指令,因为我恰恰想在没有结果的时候阻塞。
我正在为 SqlServer 和 Oracle 使用 NHibernate 3.3。
最佳答案
我终于在数据库上实现了一个信号量来解决这个问题。正如我在上面与 Frédéric 的“讨论”中提到的,我需要在选择时锁定线程以避免重复插入,Serializable 隔离级别,锁定 INSERT 并在 SQL Server 上的并发调用中调用插入时抛出死锁异常。通过其他方式,在 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;
}
我对实际代码进行了一些调整以更好地理解。
- ExecuteOnNewSession,启动一个新的隔离的 ReadCommitted 事务。因此它不会干扰已打开的事务,避免非受控锁超时和死锁并减少风险时间。
- LockSempahore:执行选择查询,锁定特定行。
我已经尝试过了,在 SQL Server 和 Oracle 上运行良好。
编辑: 为了检查可序列化事务的解决方案是否适合我,我在两个并发事务上使用了那个简单的 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;
关于c# - NHibernate 锁定数据库表以避免插入 "duplicates",我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43683920/