C#锁定SQL Server表的方法

标签 c# sql .net sql-server locking

我有一个 C# 程序需要对 SQL Server 表执行一组批量更新 (20k+)。由于其他用户可以通过Intranet网站一次更新这些记录,因此我们需要构建具有锁定表功能的C#程序。一旦表被锁定以防止其他用户进行任何更改/搜索,我们将需要执行请求的更新/插入。

由于我们要处理如此多的记录,我们不能使用 TransactionScope(起初似乎是最简单的方法),因为我们的交易最终由 MSDTC service 处理.我们需要使用另一种方法。

根据我在 Internet 上阅读的内容,使用 SqlTransaction 对象似乎是最好的方法,但是我无法锁定表。当程序运行并且我单步执行下面的代码时,我仍然能够通过内部网站执行更新和搜索。

我的问题是双重的。我是否正确使用了 SqlTransaction?如果是这样(或者即使不是)是否有更好的方法来获取允许当前运行的程序搜索和执行更新的表锁?

我希望在程序执行下面的代码时锁定表。

C#

SqlConnection dbConnection = new SqlConnection(dbConn);

dbConnection.Open();

using (SqlTransaction transaction = dbConnection.BeginTransaction(IsolationLevel.Serializable))
{
    //Instantiate validation object with zip and channel values
    _allRecords = GetRecords();
    validation = new Validation();
    validation.SetLists(_allRecords);

    while (_reader.Read())
    {
        try
        {
            record = new ZipCodeTerritory();
            _errorMsg = string.Empty;

            //Convert row to ZipCodeTerritory type
            record.ChannelCode = _reader[0].ToString();
            record.DrmTerrDesc = _reader[1].ToString();
            record.IndDistrnId = _reader[2].ToString();
            record.StateCode = _reader[3].ToString().Trim();
            record.ZipCode = _reader[4].ToString().Trim();
            record.LastUpdateId = _reader[7].ToString();
            record.ErrorCodes = _reader[8].ToString();
            record.Status = _reader[9].ToString();
            record.LastUpdateDate = DateTime.Now;

            //Handle DateTime types separetly
            DateTime value = new DateTime();
            if (DateTime.TryParse(_reader[5].ToString(), out value))
            {
                record.EndDate = Convert.ToDateTime(_reader[5].ToString());
            }
            else
            {
                _errorMsg += "Invalid End Date; ";
            }
            if (DateTime.TryParse(_reader[6].ToString(), out value))
            {
                record.EffectiveDate = Convert.ToDateTime(_reader[6].ToString());
            }
            else
            {
                _errorMsg += "Invalid Effective Date; ";
            }

            //Do not process if we're missing LastUpdateId
            if (string.IsNullOrEmpty(record.LastUpdateId))
            {
                _errorMsg += "Missing last update Id; ";
            }

            //Make sure primary key is valid
            if (_reader[10] != DBNull.Value)
            {
                int id = 0;
                if (int.TryParse(_reader[10].ToString(), out id))
                {
                    record.Id = id;
                }
                else
                {
                    _errorMsg += "Invalid Id; ";
                }
            }

            //Validate business rules if data is properly formatted
            if (string.IsNullOrWhiteSpace(_errorMsg))
            {
                _errorMsg = validation.ValidateZipCode(record);
            }

            //Skip record if any errors found
            if (!string.IsNullOrWhiteSpace(_errorMsg))
            {
                _issues++;

                //Convert to ZipCodeError type in case we have data/formatting errors
                _errors.Add(new ZipCodeError(_reader), _errorMsg);
                continue;
            }
            else if (flag)
            {
                //Separate updates to appropriate list
                SendToUpdates(record);
            }
        }
        catch (Exception ex)
        {
            _errors.Add(new ZipCodeError(_reader), "Job crashed reading this record, please review all columns.");
            _issues++;
        }
    }//End while


    //Updates occur in one of three methods below. If I step through the code,
    //and stop the program here, before I enter any of the methods, and then 
    //make updates to the same records via our intranet site the changes
    //made on the site go through. No table locking has occured at this point. 
    if (flag)
    {
        if (_insertList.Count > 0)
        {
            Updates.Insert(_insertList, _errors);
        }
        if (_updateList.Count > 0)
        {
            _updates = Updates.Update(_updateList, _errors);
            _issues += _updateList.Count - _updates;
        }
        if (_autotermList.Count > 0)
        {
            //_autotermed = Updates.Update(_autotermList, _errors);
            _autotermed = Updates.UpdateWithReporting(_autotermList, _errors);
            _issues += _autotermList.Count - _autotermed;
        }
    } 

    transaction.Commit();
}

最佳答案

SQL 并没有真正提供独占锁定表的方法:它旨在尝试在保持 ACID 的同时最大限度地提高并发使用率。

您可以尝试在您的查询中使用这些表提示:

  • 阻止

    Specifies that the acquired lock is applied at the table level. The type of lock that is acquired depends on the statement being executed. For example, a SELECT statement may acquire a shared lock. By specifying TABLOCK, the shared lock is applied to the entire table instead of at the row or page level. If HOLDLOCK is also specified, the table lock is held until the end of the transaction.

  • TABLOCKX

    Specifies that an exclusive lock is taken on the table.

  • 升级

    Specifies that update locks are to be taken and held until the transaction completes. UPDLOCK takes update locks for read operations only at the row-level or page-level. If UPDLOCK is combined with TABLOCK, or a table-level lock is taken for some other reason, an exclusive (X) lock will be taken instead.

  • 锁定

    Specifies that exclusive locks are to be taken and held until the transaction completes. If specified with ROWLOCK, PAGLOCK, or TABLOCK, the exclusive locks apply to the appropriate level of granularity.

  • 锁住/可序列化

    Makes shared locks more restrictive by holding them until a transaction is completed, instead of releasing the shared lock as soon as the required table or data page is no longer needed, whether the transaction has been completed or not. The scan is performed with the same semantics as a transaction running at the SERIALIZABLE isolation level. For more information about isolation levels, see SET TRANSACTION ISOLATION LEVEL (Transact-SQL).

或者,您可以尝试 SET TRANSACTION ISOLATION LEVEL SERIALIZABLE:

  • Statements cannot read data that has been modified but not yet committed by other transactions.

  • No other transactions can modify data that has been read by the current transaction until the current transaction completes.

  • Other transactions cannot insert new rows with key values that would fall in the range of keys read by any statements in the current transaction until the current transaction completes.

Range locks are placed in the range of key values that match the search conditions of each statement executed in a transaction. This blocks other transactions from updating or inserting any rows that would qualify for any of the statements executed by the current transaction. This means that if any of the statements in a transaction are executed a second time, they will read the same set of rows. The range locks are held until the transaction completes. This is the most restrictive of the isolation levels because it locks entire ranges of keys and holds the locks until the transaction completes. Because concurrency is lower, use this option only when necessary. This option has the same effect as setting HOLDLOCK on all tables in all SELECT statements in a transaction.

但几乎可以肯定的是,锁升级会导致阻塞,您的用户将几乎陷入困境(以我的经验)。

所以...

等到您有一个计划维护窗口。将数据库设置为单用户模式,进行更改并将其重新联机。

关于C#锁定SQL Server表的方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21712926/

相关文章:

c# - 基于变量动态访问 ASP.NET 控件...这可能吗?

c# - 将 IEnumerable 动态转换为 IQueryable 或使用 LINQ 表达式动态调用 AsQueryable

sql - sql连续几天

mysql - 如何在 SQL 中定义一个表,其中 2 个字段是非空(强制)依赖的?

c# - 无法访问添加到类库中的新类

c# - System.Core.dll 中发生类型为 'System.StackOverflowException' 的未处理异常

c# - 如何通过用户的 XAML 将控件动态添加到 UserControl?

java - 瓦丁 : commit new Items to Database (SQLContainer)

c# - 绑定(bind)到 WPF 中元素相对于父级的位置

.net - 缺少授权 header