C#方法锁定SQL Server表

Nea*_*alR 11 .net c# sql sql-server locking

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

由于我们正在处理如此多的记录,因此我们无法使用TransactionScope(这似乎是最简单的方式),因为我们的交易最终由MSDTC服务处理.我们需要使用另一种方法.

基于我在互联网上阅读的内容,使用SqlTransaction对象似乎是最好的方法,但是我无法让桌子锁定.当程序运行并且我单步执行下面的代码时,我仍然可以通过Intranet站点执行更新和搜索.

我的问题是双重的.我使用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();
}
Run Code Online (Sandbox Code Playgroud)

Nic*_*rey 7

SQL并没有真正提供一种独占锁定表的方法:它旨在尝试最大化并发使用,同时保持ACID.

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

  • TABLOCK

    指定在表级别应用获取的锁定.获取的锁类型取决于正在执行的语句.例如,SELECT语句可以获取共享锁.通过指定TABLOCK,共享锁将应用于整个表而不是行或页级.如果还指定了HOLDLOCK,表锁将保持到事务结束.

  • TABLOCKX

    指定对表执行独占锁定.

  • UPDLOCK

    指定在事务完成之前采用并保持更新锁.UPDLOCK仅在行级或页级别为读取操作获取更新锁.如果UPDLOCK与TABLOCK结合使用,或者由于其他原因而采用表级锁定,则将采用独占(X)锁定.

  • XLOCK

    指定在事务完成之前采用并保持独占锁.如果使用ROWLOCK,PAGLOCK或TABLOCK指定,则排它锁适用于适当的粒度级别.

  • HOLDLOCK/SERIALIZABLE

    通过保持共享锁更加严格,直到事务完成,而不是在不再需要所需的表或数据页时释放共享锁,无论事务是否已完成.执行扫描的语义与在SERIALIZABLE隔离级别运行的事务相同.有关隔离级别的更多信息,请参阅SET TRANSACTION ISOLATION LEVEL(Transact-SQL).

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

  • 语句无法读取已修改但尚未由其他事务提交的数据.

  • 在当前事务完成之前,没有其他事务可以修改当前事务已读取的数据.

  • 其他事务无法插入新行,其键值将落在当前事务中任何语句读取的键范围内,直到当前事务完成为止.

范围锁定位于与事务中执行的每个语句的搜索条件匹配的键值范围内.这会阻止其他事务更新或插入任何符合当前事务执行的任何语句的行.这意味着如果事务中的任何语句第二次执行,它们将读取同一组行.范围锁保持到事务完成.这是隔离级别中最具限制性的,因为它会锁定整个键范围并保持锁定直到事务完成.由于并发性较低,因此仅在必要时使用此选项.此选项与在事务中所有SELECT语句中的所有表上设置HOLDLOCK具有相同的效果.

但几乎可以肯定的是,锁定升级会导致阻塞,并且您的用户将在水中死亡(根据我的经验).

所以...

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