多个独立数据库事务的并发问题?

发布于 2025-01-05 09:11:30 字数 1917 浏览 8 评论 0原文

您将如何使用以下代码解决并发问题?在此示例中,我们想知道用户身份验证原因。问题是这段代码对数据库进行了两次单独的调用,但我们希望整个方法发生在概念事务内。具体来说,我们对隔离感兴趣。在确定身份验证失败的原因之前,我们不希望执行此方法期间的并发写入影响我们的读取。

我想到了一些解决方案:线程锁定、TransactionScope乐观锁定。我真的很喜欢乐观锁定的想法,因为我认为冲突可能很少见,但 .NET 中没有内置任何东西可以做到这一点,对吧?

另外——在这种情况下,这真的是值得关注的事情吗?什么时候需要考虑这样的并发问题,什么时候不重要?实施解决方案时需要考虑什么?表现?锁的持续时间?发生冲突的可能性有多大?

编辑:在查看了 Aristos 的答案后,我认为我真正想要的是某种“snapshot" Authenticate 方法的隔离级别。

public MembershipStatus Authenticate(string username, string password)
    {
        MembershipUser user = Membership.GetUser(username);
        if (user == null)
        {
            // user did not exist as of Membership.GetUser
            return MembershipStatus.InvalidUsername;
        }

        if (user.IsLockedOut)
        {
            // user was locked out as of Membership.GetUser
            return MembershipStatus.AccountLockedOut;
        }

        if (Membership.ValidateUser(username, password))
        {
            // user was valid as of Membership.ValidateUser
            return MembershipStatus.Valid;
        }

        // user was not valid as of Membership.ValidateUser BUT we don't really
        // know why because we don't have ISOLATION.  The user's status may have changed
        // between the call to Membership.GetUser and Membership.ValidateUser.
        return MembershipStatus.InvalidPassword;
    }

How would you solve the concurrency issue with the following code? In this example, we'd like to know why the user failed authentication. The problem is that this code makes two separate calls to the database, but we'd like the whole method to happen inside of a conceptual transaction. Specifically we're interested in isolation. We don't want concurrent writes during the execution of this method to affect our reads before we've determined the reason for the authentication failure.

A few solutions come to mind: Thread Locking, TransactionScope and Optimistic Locking. I really like the idea of Optimistic Locking, since I think conflicts are likely to be rare, but there's nothing built into .NET to do this, right?

Also - is this something to REALLY be concerned about in this case? When are concurrency issues like this important to consider and when aren't they? What needs to be considered in implementing a solution? Performance? The duration of the lock? How likely conflicts are to occur?

Edit: After reviewing Aristos' answer, I think what I'm really after is some sort of "snapshot" isolation level for the Authenticate method.

public MembershipStatus Authenticate(string username, string password)
    {
        MembershipUser user = Membership.GetUser(username);
        if (user == null)
        {
            // user did not exist as of Membership.GetUser
            return MembershipStatus.InvalidUsername;
        }

        if (user.IsLockedOut)
        {
            // user was locked out as of Membership.GetUser
            return MembershipStatus.AccountLockedOut;
        }

        if (Membership.ValidateUser(username, password))
        {
            // user was valid as of Membership.ValidateUser
            return MembershipStatus.Valid;
        }

        // user was not valid as of Membership.ValidateUser BUT we don't really
        // know why because we don't have ISOLATION.  The user's status may have changed
        // between the call to Membership.GetUser and Membership.ValidateUser.
        return MembershipStatus.InvalidPassword;
    }

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

一人独醉 2025-01-12 09:11:30

根据我阅读的此处这里,它看起来像是一个 System.Transactions.TransactionScope 包装您的整个方法应该自动在公共事务中登记您的数据库调用,从而在整个事务范围内实现事务安全。

你想做这样的事情:

public MembershipStatus Authenticate(string username, string password)
{
    using (TransactionScope scope = new TransactionScope(TransactionScopeOption.Required, new TransactionOptions { IsolationLevel = IsolationLevel.Snapshot }))
    {
    MembershipUser user = Membership.GetUser(username);
        if (user == null)
        {
            // user did not exist as of Membership.GetUser
            return MembershipStatus.InvalidUsername;
        }

        if (user.IsLockedOut)
        {
            // user was locked out as of Membership.GetUser
            return MembershipStatus.AccountLockedOut;
        }

        if (Membership.ValidateUser(username, password))
        {
            // user was valid as of Membership.ValidateUser
            return MembershipStatus.Valid;
        }

        // user was not valid as of Membership.ValidateUser BUT we don't really
        // know why because we don't have ISOLATION.  The user's status may have changed
        // between the call to Membership.GetUser and Membership.ValidateUser.
        return MembershipStatus.InvalidPassword;
    }
}

Based on my reading here and here, it seems like a System.Transactions.TransactionScope wrapping your whole method should automatically enlist your database calls in a common transaction, resulting in transactional safety across the whole Transaction scope.

You'd want to do something like this:

public MembershipStatus Authenticate(string username, string password)
{
    using (TransactionScope scope = new TransactionScope(TransactionScopeOption.Required, new TransactionOptions { IsolationLevel = IsolationLevel.Snapshot }))
    {
    MembershipUser user = Membership.GetUser(username);
        if (user == null)
        {
            // user did not exist as of Membership.GetUser
            return MembershipStatus.InvalidUsername;
        }

        if (user.IsLockedOut)
        {
            // user was locked out as of Membership.GetUser
            return MembershipStatus.AccountLockedOut;
        }

        if (Membership.ValidateUser(username, password))
        {
            // user was valid as of Membership.ValidateUser
            return MembershipStatus.Valid;
        }

        // user was not valid as of Membership.ValidateUser BUT we don't really
        // know why because we don't have ISOLATION.  The user's status may have changed
        // between the call to Membership.GetUser and Membership.ValidateUser.
        return MembershipStatus.InvalidPassword;
    }
}
扭转时空 2025-01-12 09:11:30

我将使用 mutex 使用名称作为锁定参数,因此只有同一用户可以锁定一段时间。对我来说,这对于一台计算机来说更安全,因为使用互斥锁,我可以捕获来自不同池或网络调用的所有可能的线程。

public MembershipStatus AuthenticateLock(string username, string password)
{
    if(string.IsNullOrEmpty(username))
      return MembershipStatus.InvalidUsername;

    // TODO: Here you must check and clear for non valid characters on mutex name
    using (var mutex = new Mutex (false, username))
    {
         // possible lock and wait, more than 16 seconds and the user can go...
         mutex.WaitOne (TimeSpan.FromSeconds(16), false);

         // here I call your function anyway ! and what ever done...
         //  at least I get a result
         return Authenticate(username, password)
    }
}

更多评论:Membership.ValidateUserMembership.GetUser 都会调用数据库。

但是,如果您对进行此调用并影响此参数的页面使用标准的 asp.net 会话那么页面已准备好锁定另一个,所以我认为没有机会需要这个互斥调用。因为会话的锁足以同步和这部分。我提醒一下,会话从头到尾都为所有用户锁定页面。

关于会话锁:
完全替换 ASP.Net 的会话

对Web服务的jQuery Ajax调用似乎是同步的

I will use mutex using the name as locking parametre, so only the same user may can lock out for awhile. This for me is more safe for one computer because with mutex I can capture all possible threads from different pools, or web calls.

public MembershipStatus AuthenticateLock(string username, string password)
{
    if(string.IsNullOrEmpty(username))
      return MembershipStatus.InvalidUsername;

    // TODO: Here you must check and clear for non valid characters on mutex name
    using (var mutex = new Mutex (false, username))
    {
         // possible lock and wait, more than 16 seconds and the user can go...
         mutex.WaitOne (TimeSpan.FromSeconds(16), false);

         // here I call your function anyway ! and what ever done...
         //  at least I get a result
         return Authenticate(username, password)
    }
}

More comments: Both Membership.ValidateUser and Membership.GetUser make call to the database.

But if you use the standard asp.net session for the pages that make this calls and affect this parameters, then the page all ready lock the one the other so I think that there is no chance to need this mutex call. Because the lock of the session is enough to synchronize and this part. I remind that the session is locking the page from the start to the end for all users.

About session lock:
Replacing ASP.Net's session entirely

jQuery Ajax calls to web service seem to be synchronous

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文