当前位置: 首页 > 知识库问答 >
问题:

锁定数据库表以避免插入“重复项”

阚小云
2023-03-14

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

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# 的锁定指令,因为该过程在多个服务器中执行,并且我不能使用 NHibernate 的锁定指令,因为当我没有结果时,我恰恰想阻止。

对于SqlServer和Oracle,我使用的是NHibernate 3.3。

共有2个答案

岑经纶
2023-03-14

您可以使用< code>IsolationLevel用事务锁定您的表。可序列化。

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值。点击此处阅读更多信息。有关比.Net Framework更完整的官方文档,请阅读此处和此处。

如果两个或多个进程(或线程)试图执行并发的冲突插入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).
        }
    }
}

注意:< br> 1。根据DB锁定的范围,不仅仅是关于要插入的实际值的冲突。强烈建议使用适当的索引来缩小这个范围。如果没有,可能会导致整个表被锁定,从而导致并发插入不同值的能力非常差。

司寇星海
2023-03-14

我终于在数据库上实现了信号量来解决这个问题。正如我在上面与 Frédric 的“讨论”中提到的,我需要在 select 处锁定线程以避免重复插入,可序列化隔离级别锁定在 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,启动一个新的隔离的 ReadCommit 事务。因此,它不会干扰打开的事务,以避免不受控制的锁和死锁超时,并减少风险时间。
  • 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;
 类似资料:
  • 问题内容: 某些数据库功能(例如和)很容易受到死锁的影响,因为数据库未指定将使用哪种锁定顺序。我发现有两次 讨论暗示此行为不是SQL标准指定的,更不用说具体的实现了。因此,我在假设我们无法控制锁定顺序的情况下进行操作(至少,这样做并不明显)。 如果我们不能依赖锁定顺序,应该如何避免数据库死锁? 如果我们不应该避免僵局(您将不得不非常努力地说服我),那么我们应该怎么做? 这个问题与数据库无关,所以请

  • 我有一个非常简单的表,有3列,我需要一个尽可能轻量级的查询,只有当列有新值时才能插入。 如何编写sql查询来实现这一点?我在网站上已经看到了一些例子,但它们都被我无法理解的更复杂的查询(一些涉及子查询)所混淆。 似乎有不同的方法来做这件事,我需要找到最轻量级的一个,这样我就可以在循环中重复它,一次插入多个标签,而不会给服务器带来太大的压力。

  • 我有一个严重的问题,我没有弄清楚。我有一个表名叫做“结果”。我想要实现的是,当我插入新记录时,它会正确地插入数据库。 在这个水平上,我的脚本运行良好。但是我想第二次点击添加新结果记录时,如果之前输入了数据,那么它将显示我的数据,如果我想更新我的数据,我可以。如果以前没有输入数据,那么我会将数据插入数据库。我成功地限制用户输入重复数据,但我没有成功地在同一页上显示数据。 我有一个显示测试页面,当我点

  • 我有一个简单的项目,该项目将个人信息提供给数据库(sqlite),现在我想避免插入重复值(按名称),我不能做什么?谢谢你们

  • 问题内容: 我有一个包含3列的表格- id(pk),pageId(fk),名称。我有一个PHP脚本,它将大约5000条记录转储到表中,其中大约一半是重复的,具有相同的pageId和名称。pageId和名称的组合应该是唯一的。当我遍历php中的脚本时,防止重复项被保存到表中的最佳方法是什么? 问题答案: 第一步是在表上设置唯一键: 然后,当有重复项时,您必须决定要做什么。你应该: 忽略它? 覆盖先前

  • 如何避免在PHP MYSQLi中插入重复记录?这是我的剧本: