2009-11-10 2 views
3

У меня возникает вопрос, почему какой-то SQL (работает на SQL Server 2005) ведет себя так, как есть. В частности, я сделал изменения, чтобы уменьшить конфликт блокировок во время обновления, и, похоже, он работает в тех случаях, когда я не думал, что это произойдет.Почему SQL Update Top, по-видимому, снижает блокировку, даже когда никакие записи не обновляются?

Оригинальный код:

У нас было обновление заявление, как это, что в настоящее время применяется к таблице с более чем 3000000 записей:

UPDATE USER WITH (ROWLOCK) 
SET Foo = 'N', Bar = getDate() 
WHERE ISNULL(email, '') = '' 
AND Foo = 'Y' 

Как вы можете догадаться, это, казалось, временно заблокируйте таблицу USER. Даже с подсказкой ROWLOCK другие задания, выполняющие запросы и обновления против USER, блокируются до тех пор, пока это не будет выполнено. Это неприемлемо для этого конкретного приложения, поэтому я подумал, что применил бы трюк, о котором я читал, если инструкция обновления обновляет только 100 записей за раз. Это дало бы другим запросам возможность иногда появляться за столом.

Улучшен код:

DECLARE @LOOPAGAIN AS BIT; 
SET @LOOPAGAIN = 1; 

WHILE @LOOPAGAIN = 1 
    BEGIN 
    UPDATE TOP (100) USER WITH (ROWLOCK) 
    SET Foo = 'N', Bar = getDate() 
    WHERE ISNULL(email, '') = '' 
    AND Foo = 'Y' 

    IF @@ROWCOUNT > 0 
     SET @LOOPAGAIN = 1 
    ELSE 
     SET @LOOPAGAIN = 0 
    END 

Это сделал трюк. Наше обновление выполнило свою работу, и другие запросы смогли получить за столом. Все это счастье и свет.

Тайна:

Я понимаю, как это улучшило производительность, когда было много записей в таблице это должно было обновить. Проведя быстрый цикл через каждые 100 обновлений, он дал другим запросам возможность получить за стол. Тайна в том, что эта петля имела тот же эффект, даже если на обновление не повлияло обновление!

Во второй раз, когда мы выполнили наш первоначальный запрос, он будет работать только в течение половины времени (например, около 30 секунд), но за это время он заблокирует таблицу, даже если никакие записи не изменялись. Но поставим запрос в цикле с предложением «TOP (100)», и хотя потребовалось столько же времени, чтобы ничего не делать, он высвободил таблицу для других запросов!

Я очень удивлен этим. Может кто-нибудь сказать мне:

  1. Если то, что я только что сказал, совсем ясно, и,
  2. Почему второй блок кода позволяет другие запросы, чтобы получить за столом, даже если нет записей обновляемые?
+0

Я считаю, что он все еще должен «ВЫБРАТЬ» перед таблицей, прежде чем он осознает, что для обновления нет строк. – 2009-11-10 20:32:43

+1

На каком уровне изоляции транзакций? –

ответ

3

Это звучит как классический случай эскалации блокировки.

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

  1. SQL Server 2005 будет эскалатировать вашу блокировку, когда 5000 блокировок будут получены на одной таблице или индексе. Для этого есть оговорки и исключения, поэтому см. Lock Escalation (Database Engine) для получения дополнительной информации.
  2. Блокировочные подсказки, такие как ROWLOCK, не предотвратить эскалацию блокировки.
  3. «Механизм базы данных не увеличивает количество блокировок на уровне строки или ключа до , блокирует страницы, но увеличивает их непосредственно на столовые замки».

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

Рекомендации, чтобы избежать эскалации блокировок являются:

  1. Разбейте крупные операции на более мелкие операции (как вы сделали это!).
  2. Настройте свой запрос как эффективный насколько это возможно.
  3. В качестве последнего средства вы можете установить трассировку флаг 1211, чтобы отключить эскалацию блокировки (не рекомендуется!).

Для получения более подробной информации см. How to resolve blocking problems that are caused by lock escalation in SQL Server.

Если вы хотите проверить, что эскалация блокировки - это то, что происходит, вы можете использовать использование SQL Server Profiler и посмотреть событие Lock: Escalation.

+0

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

0
  1. Это ясно.
  2. Эти условия ОЧЕНЬ ТЯЖЕЛОГО ISNULL(email, '') = '' AND Foo = 'Y'.

Обновление ищет все строки, которые могут потребоваться для обновления, поэтому он принимает одно и то же время, даже если нет строк для обновления.

Это слепой снимок, но вы должны рассмотреть возможность добавления индекса как для полей Email, так и для полей Foo (не для каждого индекса, для обоих из них).

Это только тяжелый запрос, который вы делаете на этом столе?Какие индексы указаны в этой таблице?

+0

И вы можете еще больше улучшить использование индекса, удалив вызов 'ISNULL' в разделе' WHERE'. Оптимизатор предпочтет что-то вроде 'WHERE (email IS NULL ИЛИ email = '') AND foo = 'Y''. – LukeH

+0

@ Luke: Это реально? isnull (email, '') = '' НЕ интерпретируется им как (по электронной почте null или email = '')? – Ice

0

Кажется, что SQL Server выбирает другую блокировку, основанную на TOP 200, хотя вы указываете ROWLOCK. Вы видите какую-либо разницу в Management -> Activity Montior, под Locks by Object?

0

Вы должны также рассмотреть рефакторинга к обновлению от того, если с большими таблицами, в моем опыте они выполняют лучше:

UPDATE USER Foo = 'N', Бар = GetDate() FROM (SELECT user.id ОТ ПОЛЬЗОВАТЕЛЯ // опциональной NOLOCK Подсказка, если вы не заботитесь о незавершенных чтения. ГДЕ COALESCE (EMAIL, '') = '' И Foo = 'Y') D WHERE D.ID = USER.ID

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