Как настроить блокировку для операции чтения-манипуляции-записи? - PullRequest
5 голосов
/ 18 января 2011

Учитывая следующую таблицу

Key(KeyId int, Sequence varchar(14))

Значение последовательности - это пользовательский ключ автоинкремента, объединяющий буквы и цифры, которые нужны конкретному клиенту для его системы.

Мы создали функцию GetNextSequence (), которая должна возвращать следующее значение последовательности. Шаг для чтения и обновления последовательности идет следующим образом

  1. Считать значение последовательности, используя KeyId: SELECT Sequence FROM [Key] WHERE KeyId = @Id
  2. Разобрать значение последовательности и определить следующее значение
  3. Запишите значение последовательности в таблицу: UPDATE [Key] SET Sequence = @Sequence WHERE KeyId = @Id

Вот код C # (упрощенный для ясности):

var transaction = connection.BeginTransaction(IsolationLevel.RepeatableRead);
var currentSequenceValue = SqlUtils.ExecuteScalar(connection, transaction, "SELECT Sequence FROM [Key] WHERE KeyId = @Id", new SqlParameter("@Id", keyId));
var updatedSequenceValue = ParseSequence(currentSequenceValue);
SqlUtils.ExecuteScalar(connection, transaction, "UPDATE [Key] SET Sequence = @Sequence WHERE KeyId = @Id", new SqlParameter("@Id", keyId), new SqlParameter("@Sequence", updatedSequenceValue));
transaction.Commit();
return updatedSequenceValue;

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

Транзакция (идентификатор процесса X) заблокирована для ресурсов блокировки с другим процессом и выбрана в качестве жертвы тупика. Перезапустите транзакцию.

В C # я пытался установить другую комбинацию блокировок, например изоляцию транзакции IsolationLevel.RepeatableRead или IsolationLevel.Serializable, или в SQL, используя табличные подсказки ROWLOCK и HOLDLOCK, но безуспешно.

Я хочу, чтобы каждый сервер мог читать, манипулировать и обновлять последовательность атомарным способом. Как правильно настроить блокировку для этой ситуации?

Ответы [ 2 ]

2 голосов
/ 18 января 2011

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

Ситуация такова, что процесс A получает блокировку чтения в строке X. Затем процесс B получает блокировку считывателя, пока A работает на своей "клиентской стороне" (в программе сервера). Затем A запрашивает обновление до блокировки записи, пока B работает на стороне клиента, и в этот момент ему говорят ждать, пока блокировка чтения B не будет снята. Затем B запрашивает блокировку записи и ожидает, пока A не выпустит чтение. Оба теперь ждут другого, чтобы получить более эксклюзивную блокировку записи.

Решение - эксклюзивный замок; Вы можете указать это, используя подсказку XLOCK. Исключительная блокировка - это, по сути, блокировка уровня записи, полученная для чтения, и используется именно в этом случае, когда вы ожидаете написать что-то, что читаете. Как отмечено в комментариях, исключительная блокировка поддерживается только в том случае, если инструкция выполняется в рамках явной транзакции, поэтому убедитесь, что вы устанавливаете ее во время «единицы работы», которая считывает значение, определяя, как выполнить это, а затем обновить его.

Я бы использовал это на уровне строк (ROWLOCK), если вы не обновляете много похожих последовательностей одновременно; получение эксклюзивной блокировки на уровне страницы или таблицы заставляет ВСЕХ ждать данных, которые вам не нужны, если вы работаете только с одной строкой на транзакцию.

1 голос
/ 18 января 2011

Я предлагаю использовать эксклюзивную блокировку на уровне строк на время транзакции (ROWLOCK, XLOCK, HOLDLOCK).Использование вами подсказок и т. Д. Пока недостаточно.

BEGIN TRAN
    SELECT Sequence FROM [Key] WITH (ROWLOCK, XLOCK, HOLDLOCK) WHERE KeyId = @Id

    Parse the sequence value and determine the next value

    UPDATE [Key] SET Sequence = @Sequence WHERE KeyId = @Id
COMMIT

Хотя я бы посмотрел, по крайней мере, на то, чтобы уменьшить область действия до одной транзакции.не нужен HOLDLOCK, если вы используете SERIALIZABLE.И «RepeatableRead» может быть недостаточно из-за того, как диапазоны заблокированы

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...