C# 锁定 SQL Server 表的方法

2024-03-30

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

由于我们正在处理如此多的记录,因此我们无法使用TransactionScope(一开始似乎是最简单的方法)由于我们的交易最终由MSDTC服务 http://en.wikipedia.org/wiki/Microsoft_Distributed_Transaction_Coordinator。我们需要使用另一种方法。

根据我在互联网上阅读的内容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();
}

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

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

  • TABLOCK

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

  • TABLOCKX

    指定对表采取排他锁。

  • UPDLOCK

    指定要获取并保留更新锁,直到事务完成。 UPDLOCK 仅在行级或页级对读操作获取更新锁。如果 UPDLOCK 与 TABLOCK 结合使用,或者对其他一些进行表级锁定 原因是,将采用独占 (X) 锁。

  • XLOCK

    指定要获取并保持独占锁,直到事务完成 完成。如果使用 ROWLOCK、PAGLOCK 或 TABLOCK 指定,则应用独占锁 到适当的粒度级别。

  • 锁定/可串行化

    通过持有共享锁直到事务完成来使共享锁更具限制性, 而不是一旦需要的表或数据页没有就释放共享锁 无论交易是否完成,都需要更长的时间。扫描结果是 使用与在 SERIALIZABLE 上运行的事务相同的语义执行 隔离级别。有关隔离级别的更多信息,请参阅设置事务 隔离级别 (Transact-SQL)。

或者,您可以尝试设置事务隔离级别可串行化:

  • 语句无法读取已被其他人修改但尚未提交的数据 交易。

  • 其他事务不能修改当前事务已读取的数据 直到当前事务完成。

  • 其他事务无法插入具有属于该值的键值的新行 当前事务中任何语句读取的键范围,直到当前 交易完成。

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

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

So...

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

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C# 锁定 SQL Server 表的方法 的相关文章

随机推荐