2014-02-11 5 views
11

У меня есть программа на C#, которая должна выполнять группу массовых обновлений (20k +) в таблице SQL Server. Поскольку другие пользователи могут обновлять эти записи по одному через веб-сайт интрасети, нам необходимо создать программу C# с возможностью блокировки таблицы. После того, как таблица заблокирована, чтобы другой пользователь не выполнял никаких изменений/поиска, нам необходимо предварительно запрограммировать запрошенные обновления/вставки.C# метод блокировки таблицы SQL Server

Поскольку мы обрабатываем так много записей, мы не можем использовать TransactionScope (казалось, это был самый простой способ) из-за того, что наша транзакция завершается обработкой MSDTC service. Нам нужно использовать другой метод.

Основываясь на том, что я читал в Интернете, используя объект 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(); 
} 
+1

Что Updates.Insert и Updates.Update делать? Возможно, вам придется опубликовать этот код. Используют ли они другое соединение? В любом случае вы выполняете BeginTransaction, но не устанавливаете эту транзакцию для своих объектов Command, выполняющих обновления. Кроме того, вы никогда не вызываете Transaction.Commit(); Чтобы проверить это, в то время как код запускает ваш цикл, перейдите в SSMS и выберите @@ trancount. Если 0, то транзакция не используется. –

+0

Как сказал Дэвид, транзакция не привязана ни к чему. Он ничего не делает. Зачем вам нужно блокировать всю таблицу для 20+ обновлений K? На основе опубликованного кода эти обновления независимы. – Paparazzi

+0

Нам нужно заблокировать таблицу, так как мы выполняем проверку и обновляем/вставляем как целую группу отдельно. Мы не хотим, чтобы запись проверялась, а затем пользователь использовал сайт интрасети для внесения изменений, которые заставили бы только что подтвержденную запись внезапно нарушить ограничение или что-то еще. – NealR

ответ

7

SQL на самом деле не обеспечивает способ исключительно блокировки таблицы: он предназначен для максимизации одновременного использования при сохранении ACID.

Вы можете попробовать с помощью этих таблиц намеков на ваши вопросы:

  • TABLOCK

    Указывает, что приобретенная замок применяется на уровне таблицы. Тип блокировки, которую получает , зависит от выполняемого оператора. Например, оператор SELECT может получить общую блокировку. Указав TABLOCK, общая блокировка применяется к всей таблице вместо уровня строки или страницы.Если HOLDLOCK также указан, блокировка таблицы удерживается до конца транзакции.

  • TABLOCKX

    Указывает, что монопольная блокировка берется на столе.

  • UPDLOCK

    Указывает, что блокировки обновления должны быть приняты и проведены, пока транзакция не будет завершена. UPDLOCK использует блокировки обновления для операций чтения только на уровне строк или на уровне страницы. Если UPDLOCK объединен с TABLOCK, или блокировка на уровне стола берется для какой-либо другой причины , вместо этого будет использоваться исключающая (X) блокировка.

  • XLOCK

    Указывает, что эксклюзивные замки не должны быть приняты и проведены до сделки Завершает. Если указано значение ROWLOCK, PAGLOCK или TABLOCK, исключительные блокировки применяют к соответствующему уровню детализации.

  • HOLDLOCK/SERIALIZABLE

    Делает общие замки более ограничительные, удерживая их до сделки будет завершена, вместо того, чтобы высвобождать общую блокировку, как только требуемой таблицы или страницы данных не больше необходимо, была ли транзакция завершена или нет. Сканирование выполняется с той же семантикой, что и транзакция, выполняемая на уровне изоляции SERIALIZABLE . Дополнительные сведения об уровнях изоляции см. В разделе SET TRANSACTION УРОВЕНЬ ИЗОЛЯЦИИ (Transact-SQL).

В качестве альтернативы, вы можете попробовать SET TRANSACTION ИЗОЛЯЦИЯ УРОВЕНЬ SERIALIZABLE:

  • отчетность не может читать данные, которые были изменены, но еще не совершенные другими сделок.

  • Никакие другие транзакции не могут изменять данные, которые были прочитаны текущей транзакцией , до завершения текущей транзакции.

  • Другие транзакции не могут вставлять новые строки с ключевыми значениями, которые не подпадали бы в диапазоне ключей, считываемых любые заявления в текущей транзакции, пока текущая транзакция завершается.

Range замки находятся в диапазоне значений ключа, которые соответствуют условиям поиска по каждому выполненному оператору в транзакции.Это блокирует другие транзакции от обновления или вставляет любые строки, которые могут быть квалифицированы для любого из операторов, выполняемых текущей транзакцией . Это означает, что если какое-либо из операторов транзакции будет выполняться во второй раз, то они будут читать один и тот же набор строк. Блокировки диапазона удерживаются до завершения транзакции. Это наиболее ограничивает уровни изоляции , поскольку он блокирует все диапазоны ключей и удерживает блокировки до завершения транзакции . Поскольку параллелизм ниже, используйте эту опцию только тогда, когда это необходимо. Этот параметр имеет тот же эффект, что и установка HOLDLOCK во всех таблицах во всех операторах SELECT в транзакции.

Но почти наверняка эскалация блокировки вызовет блокирование, и ваши пользователи будут в значительной степени мертвы в воде (по моему опыту).

Итак ...

Подождите, пока у вас есть окно обслуживания по расписанию. Установите базу данных в однопользовательском режиме, внесите изменения и верните их в режиме онлайн.

2

Это все о уровне изоляции здесь. Измените уровень изоляции транзакций на ReadCommited (не просматривайте значение Enum Value в C#, но оно должно быть близко). Когда вы выполняете первое обновление/вставку в таблицу, SQL запустится, и никто не сможет прочитать данные, которые вы изменяете/добавляете, пока вы не совершаете транзакцию или не выполняете транзакцию при условии, что они не выполняют грязные чтения (используя NoLock на их SQL или установить уровень изоляции для Read Uncommited). Будьте осторожны, однако, в зависимости от того, как вы вставляете/обновляете данные, вы можете заблокировать всю таблицу в течение всей транзакции, хотя это может привести к ошибкам таймаута в клиент, когда они пытаются прочитать из этой таблицы, пока ваша транзакция открыта. Не видя SQL за обновлениями, хотя я не могу сказать, случится ли это здесь.

+0

Есть ли способ заблокировать таблицу перед обновлениями/вставками? Поскольку у нас так много записей, мы можем приблизиться к минутам между тем, когда мы выполняем нашу проверку (на основе набора бизнес-правил) и когда происходят обновления. Мы хотели бы заблокировать второй, который мы получаем в коде, и начать проверку набора данных в целом. – NealR

3

Попробуйте это: когда вы получите записи из вас таблицы (в GetRecords() функцию?) Использовать TABLOCKX подсказка:

SELECT * FROM Table1 (TABLOCKX) 

Он будет стоять в очереди все другие операции чтения и обновления за пределами вашей транзакции, пока транзакция не будет либо завершен или откат.

+0

Даже если он будет стоять в очереди 20k + обновлений, которые не будут красивыми. – Paparazzi

+0

Забыл об этом .. Но я запираю весь стол совсем не красиво. – MBulava

1

Как указал кто-то, сделка, по-видимому, не используется после изъятия.

Из ограниченной информации, которую мы имеем в приложении/цели, это сложно сказать, но из фрагмента кода мне кажется, что нам не нужна блокировка. Мы получаем некоторые данные из источника X (в данном случае _reader), а затем вставляем/обновляем в пункт назначения Y.

Вся валидация происходит с исходными данными, чтобы убедиться, что она правильная, не похоже, что мы принимаем какое-либо решение или заботимся о том, что находится в пункте назначения.

Если приведенное выше значение является истинным, то лучшим подходом было бы загрузить все эти данные во временную таблицу (может быть реальная таблица темп "#" или реальная таблица, которую мы уничтожаем впоследствии, но цель - то же), а затем в одном заявлении sql мы можем сделать массовую вставку/обновление из таблицы temp в наш пункт назначения. Предполагая, что схема db находится в приличной форме, 20 (или даже 30) тысяч записей должны произойти почти мгновенно, без необходимости ждать окна обслуживания или блокировать пользователей в течение продолжительных периодов времени.

Также, чтобы строго ответить на вопрос об использовании сделка, ниже простой пример того, как правильно использовать транзакцию, там должно быть много других образцов и информации на веб-сайте

SqlConnection conn = new SqlConnection(); 
SqlCommand cmd1 = new SqlCommand(); 
SqlTransaction tran = conn.BeginTransaction(); 

... 
cmd1.Transaction = tran; 
... 
tran.Commit(); 
+0

Это отличный момент. OP, по-видимому, делает предположение, что проверка данных * до * вставки/обновления гарантирует, что данные будут вставлены/обновлены точно по запросу. Это может быть ложным по множеству причин. Проверка должна происходить * после того, как * вставки/обновления произошли, в отношении самой базы данных, предпочтительнее в рамках одной и той же транзакции, так что откат может быть выполнен, если проверка не выполняется. – Dan

Смежные вопросы