Skip to main content
 首页 » 编程设计

c#之NHibernate 锁定数据库表以避免插入 "duplicates"

2024年02月27日32cloudgamer

我认为这是一个常见问题,但我还没有找到任何解决方案,也许我没有在谷歌中正确搜索问题。总而言之,我有一个在表中插入多行的过程(在同一事务中的许多其他事物中),但该过程是在多个线程和多个服务器中执行的。

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;