Почему SQL Update Top, по-видимому, уменьшает блокировку, даже если записи не обновляются? - PullRequest
3 голосов
/ 10 ноября 2009

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

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

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

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

Как вы, наверное, догадались, это на какое-то время заблокировало таблицу USER. Даже с подсказкой ROWLOCK другие задания, выполняющие запросы и обновления для пользователя USER, будут блокироваться, пока это не будет сделано. Это неприемлемо для данного конкретного приложения, поэтому я решил применить хитрость, о которой читал, когда оператор update обновляет только 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. Почему второй блок кода позволяет другим запросам попасть в таблицу, даже если записи не обновляются?

Ответы [ 4 ]

3 голосов
/ 17 ноября 2009

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

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

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

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

Рекомендации по избежанию эскалации блокировки:

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

Подробнее см. Как устранить проблемы с блокировкой, вызванные эскалацией блокировки в SQL Server .

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

0 голосов
/ 11 ноября 2009

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

0 голосов
/ 17 ноября 2009

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

ОБНОВЛЕНИЕ ПОЛЬЗОВАТЕЛЯ Foo = 'N', Bar = getDate () FROM (ВЫБЕРИТЕ USER.ID ИЗ ПОЛЬЗОВАТЕЛЯ // необязательный NOLOCK Подсказка, если вас не волнует чтение без передачи ГДЕ КОАЛЕСС (EMAIL, '') = '' И Foo = 'Y') D ГДЕ D.ID = USER.ID

0 голосов
/ 10 ноября 2009
  1. Понятно.
  2. Эти условия ОЧЕНЬ ТЯЖЕЛЫЕ ISNULL(email, '') = '' AND Foo = 'Y'.

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

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

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

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