2016-11-14 6 views
2

У меня есть приложение, подключенное к базе данных SQL Server 2014, которая объединяет несколько строк в один. Других подключений к этой базе данных пока приложение работает.Предотвращение взаимоблокировок в SQL Server

Сначала выберите фрагмент строк в течение определенного промежутка времени. Этот запрос использует некластеризованный поиск (столбец TIME), объединенный с кластеризованным поиском.

select ... 
from FOO 
where TIME >= @from and TIME < @to and ... 

Затем мы обрабатываем эти строки в C# и записать изменения в качестве одного обновления и нескольких удалений, это происходит много раз в кусок. Они также используют некластеризованные запросы индекса.

begin tran 

update FOO set ... 
where NON_CLUSTERED_ID = @id 

delete FOO where NON_CLUSTERED_ID in (@id1, @id2, @id3, ...) 

commit 

Я получаю блокировки при работе с несколькими параллельными кусками. Я пробовал использовать ROWLOCK для update и delete, но по какой-то причине это вызвало еще больше тупиков, чем раньше, хотя между кусками нет перекрытий.

Тогда я пробовал TABLOCKX, HOLDLOCK на update, но это означает, что я не могу выполнить мой select параллельно, поэтому я теряю преимущества параллелизма.

Любая идея, как я могу избежать взаимоблокировок, но все же обрабатывать несколько параллельных кусков?

Было бы безопасно использовать NOLOCK на моем select в этом случае, если не существует перекрытия строк между кусками? Тогда TABLOCKX, HOLDLOCK будет блокировать только update и delete, правильно?

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

UPDATE (дополнительная информация): Все тупики до сих пор не произошло в фазе update и delete, ни в select. Я постараюсь сделать некоторые блокировки журналов, если я не смогу решить эту проблему сегодня (правильные флаги трассировки ранее не были включены).

UPDATE: Эти два устройство из тупиков, которые происходят с ROWLOCK, оба они относятся только к delete заявления и некластерному индексу она использует. Я не уверен, что они такие же, как тупики, которые происходят без каких-либо табличных подсказок, поскольку я не смог воспроизвести их.

Deadlock 1 Deadlock 2

Спросите, есть ли что-нибудь еще нужно от .xdl, я немного устал присоединять все это.

+0

Вы пытались претендовать на 'UPDLOCK' во время вашего выбора? Таким образом, блокировка уже существует, когда вы обновляете/удаляете, что должно удержать вас от блокировки. Если возможно, поделитесь с нами некоторыми деталями регистрации тупиков. – Jens

+1

Можете ли вы переместить всю обработку в хранимую процедуру? Вы также можете решить эту проблему, просто включив выделение снимков, но это действительно зависит от того, что вы делаете. –

+0

@Jens К сожалению, сейчас я не могу получить блокировку. Кажется, что все потоки, которые потерпели неудачу из-за взаимоблокировок, были в фазе 'update' и' delete', поэтому изменение блокировки 'select' вряд ли повлияет на этот случай. К сожалению, у меня нет журналов в тупике, я посмотрю, могу ли я включить флаги следов тупика для следующей попытки. @ Nick.McDermaid Невозможно использовать изоляцию моментальных снимков, к сожалению. –

ответ

0

Общие рекомендации относительно взаимоблокировок: убедитесь, что вы делаете все в одном порядке, то есть приобретаете блокировки в одном порядке для разных процессов.

Вы можете найти тот же совет в этой технической статье на microsoft.com относительно Minimizing Deadlocks. Есть веская причина, по которой она указана первыми.

  • доступа к объектам в том же порядке.
  • Избегайте взаимодействия с пользователем в транзакциях.
  • Держите транзакции короткими и в одной партии.
  • Используйте нижний уровень изоляции.
  • Используйте уровень изоляции на основе версии.
  • Установите для параметра базы данных READ_COMMITTED_SNAPSHOT значение ON, чтобы включить транзакции с чтением, чтобы использовать управление версиями строк.
  • Используйте изоляцию моментального снимка.
  • Используйте связанные соединения.

Update после вопроса от Катона:

Как приобретающие замки в том же порядке применить здесь? У вас есть какие-либо советы о том, как он изменит свой SQL для этого?

Тупики не всегда одинаковы, независимо от того, что окружающая среда: два процесса (скажем A & B) приобретают несколько замков (скажем X & Y) в другом порядке, так что A ждет Y и B ждет X в то время как A проводит X и B проводит Y.

Применяется здесь, потому что заявления DELETE и UPDATE неявно получают блокировки по строкам или диапазону индексов или таблице (в зависимости от того, что двигатель считает уместным).

Вы должны проанализировать свой процесс и посмотреть, есть ли сценарии, в которых блокировки могут быть приобретены в другом порядке. Если это ничего не раскрывает, вы можете analyze deadlocks using the SQL Server Profiler:

Чтобы отслеживать события блокировки, добавьте класс событий графа Deadlock к трассе. Этот класс событий заполняет столбец данных TextData в трассировке данными XML о процессе и объектах, которые задействованы в тупике. Провайдер SQL Server может извлечь XML-документ в файл deadlock XML (.xdl), который вы можете просмотреть позже в SQL Server Management Studio. Вы можете сконфигурировать SQL Server Profiler для извлечения событий Deadlock graph в один файл, который содержит все события треугольника deadlock или для разделения файлов.

+0

Как бы приобрести блокировки в том же порядке? У вас есть какие-либо советы о том, как он изменит свой SQL для этого? – Cato

+0

@Cato Дополнительный контекст в обновленном ответе. –

+0

Добавлены тупиковые графы к вопросу, оба они, похоже, ссылаются только на инструкцию 'delete' –

0

Я бы использовал sp_getapplock в транзакции обновления, чтобы предотвратить параллельное выполнение нескольких экземпляров этого кода. Это не будет блокировать оператор выбора, как подсказки для блокировки таблицы.

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

Таким образом, транзакция обновления может быть завернута в sp_getapplock.

BEGIN TRANSACTION; 
BEGIN TRY 

    DECLARE @VarLockResult int; 
    EXEC @VarLockResult = sp_getapplock 
     @Resource = 'some_unique_name_app_lock', 
     @LockMode = 'Exclusive', 
     @LockOwner = 'Transaction', 
     @LockTimeout = 60000, 
     @DbPrincipal = 'public'; 

    IF @VarLockResult >= 0 
    BEGIN 
     -- Acquired the lock 
     update FOO set ... 
     where NON_CLUSTERED_ID = @id 

     delete FOO where NON_CLUSTERED_ID in (@id1, @id2, @id3, ...) 

    END ELSE BEGIN 
     -- return some error code, so that the caller could retry 
    END; 

    COMMIT TRANSACTION; 
END TRY 
BEGIN CATCH 
    ROLLBACK TRANSACTION; 
    -- handle the error 
END CATCH; 

Операция выбора не требует никаких изменений.

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

+0

Похоже, что' sp_getapplock' будет медленнее, чем моя текущая реализация, и мне было бы лучше просто повторить все взаимоблокировки. Спасибо за информацию о 'NOLOCK'. –

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