C# C、 SQLServer-阻止表行读取,根据需要进行编辑并释放锁

C# C、 SQLServer-阻止表行读取,根据需要进行编辑并释放锁,c#,sql-server-2008,C#,Sql Server 2008,当我为同一行执行更新逻辑时,我需要阻止行级别的数据库读取。又好又干净的解决方案会是什么样子?以下是我正在编写的一些代码: using (SqlConnection conn = new SqlConnection(Configuration.ConnectionString)) { conn.Open(); using (SqlTransaction tran = conn.BeginTransaction(IsolationLevel.Serializable)) {

当我为同一行执行更新逻辑时,我需要阻止行级别的数据库读取。又好又干净的解决方案会是什么样子?以下是我正在编写的一些代码:

using (SqlConnection conn = new SqlConnection(Configuration.ConnectionString)) {
    conn.Open();
    using (SqlTransaction tran = conn.BeginTransaction(IsolationLevel.Serializable)) {
        //TODO lock table row with ID=primaryKey (block other reads and updates)
        using (SqlCommand cmd = new SqlCommand("SELECT Data FROM MyTable WHERE ID=@primaryKey", conn)) { 
            cmd.Parameters.AddWithValue("@primaryKey", primaryKey);
            using (var reader = cmd.ExecuteReader()) {
                data = PopulateData(reader);
            };
        }

        if (isUpdateNeeded(data)) {
            ChangeExternalSystemStateAndUpdateData(data) //EDIT - concurrent calls not allowed
            WriteUpdatesToDatabase(conn, tran, data);    //EDIT
            tran.Commit();
        }


    } //realease lock and allow other processes to read row with ID=primaryKey
}
编辑: 我有以下限制:

代码可以在不同的应用程序池中同时执行。所以内存锁不是一个选项 ChangeExternalSystemState和UpdateData只能在MyTable行的范围内执行一次。并发调用将导致问题
通常这里的问题不是太多的行锁定,而是:其他SPID在读取和更新之间获取读取锁定,这可能导致死锁场景;这里常见的修复方法是在初始读取时获取写锁:

使用UPDLOCK从MyTable中选择数据,其中ID=@primaryKey 请注意,如果另一个SPID显式使用NOLOCK,那么它们仍然会闪过它


您还可以尝试添加ROWLOCK,即使用UPDLOCK和ROWLOCK-就个人而言,一开始我会保持简单。由于您处于可序列化的上下文中,试图过于粒度化可能是丢失的原因-键范围锁等,而不是锁定,请考虑使用乐观并发,在其中检查行是否在更新操作期间发生了更改。有关更多信息,请参阅示例。请注意,您可以使用单个而不是传递所有原始值来检测更改。您不需要锁定行来编辑它们。这个问题是在20世纪90年代通过使用乐观并发解决的。如果要使用数据库锁来模拟签入/签出,则不能。数据库锁的寿命很短。签出/编辑/签入周期是一个业务概念,即使不是几天也需要几分钟。在应用程序中添加显式签入/签出功能,例如使用签出标志和日期、签出用户等。修改应用程序以不编辑签出记录。所有POS、文档管理应用程序都是这样工作的。如果他们在用户试图在表单上查找要更新的字段时使用数据库锁,他们将无法处理十几个并发用户。他们也无法处理意外的断开连接或错误。Web应用程序根本不能做任何事情——您不能在请求之间保持数据库锁。@PanagiotisKanavos这里没有人提到UI系统;现在,有一个问题是ChangeExternalSystemStateAndUpdateData需要多长时间-如果它太多,那么是的:乐观并发可能有助于防止块影响其他系统,但这是一把双刃剑,因为如果使用乐观并发,您需要预期它会失败,这意味着您需要能够经常补偿由于数据库更新失败而导致的外部系统;悲观并发要简单得多,如果外部更新不是非常慢,那么悲观并发可能是最好的选择。乐观并发的问题是,只有在更新过程中才会检查违规行为-这不会阻止我想要阻止的对UpdateData函数的并发调用。我主要担心的是UpdateData可能很重操作并可改变外部系统的状态。它也可以在不同的应用程序池中执行。所以我不能使用内存锁。有并没有办法确保UpdateData在MyTable行的范围内只被调用一次?@AndreTchernikov并没有人提到内存锁,不管这意味着什么;至于其余部分:这就是带有锁定的可序列化事务所做的;注意:这只意味着多次尝试这样做将按顺序运行,而不是同时运行-它是否运行多次取决于isUpdateNeeded逻辑的功能。对于如图所示的代码,竞争线程应该在ExecuteReader上被阻塞,直到竞争线程通过commit或RollbackMarcGravel完成,很抱歉内存锁混淆。UPDLOCK对我来说有点出名。所以语句在一个事务中使用UPDLOCK从MyTable中选择数据,其中ID=99。是否会在另一个事务中阻止同一个SQL select语句?@AndreTchernikov它将阻止其他SPID获得读锁或更高的读锁;在可序列化隔离级别的情况下,这意味着它将阻止任何尝试读取该隔离级别的内容,因为在可序列化隔离级别,除非指定了NOLOCK,否则默认情况下读取锁定;但是,即使没有事务/可序列化上下文,它也会阻止包含UPDLOCK提示的同一SQL语句,因为在这种情况下,您告诉它尝试使用锁和写锁;如果您的第一个SPID已经有一个写操作,那么它不能接受读或写锁lock@AndreTchernikov一个简短的版本可能是肯定的,但是:它比这更微妙和微妙