Диагностика взаимоблокировок в SQL Server 2005 - PullRequest
82 голосов
/ 21 августа 2008

Мы видим некоторые пагубные, но редкие тупиковые условия в базе данных Stack Overflow SQL Server 2005.

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

UPDATE [dbo].[Posts]
SET [AnswerCount] = @p1, [LastActivityDate] = @p2, [LastActivityUserId] = @p3
WHERE [Id] = @p0

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

SELECT
[t0].[Id], [t0].[PostTypeId], [t0].[Score], [t0].[Views], [t0].[AnswerCount], 
[t0].[AcceptedAnswerId], [t0].[IsLocked], [t0].[IsLockedEdit], [t0].[ParentId], 
[t0].[CurrentRevisionId], [t0].[FirstRevisionId], [t0].[LockedReason],
[t0].[LastActivityDate], [t0].[LastActivityUserId]
FROM [dbo].[Posts] AS [t0]
WHERE [t0].[ParentId] = @p0

Чтобы быть совершенно ясным, мы не видим тупиковые ситуации записи / записи, а читаем / пишем.

На данный момент у нас есть смесь запросов LINQ и параметризованного SQL. Мы добавили with (nolock) ко всем запросам SQL. Это, возможно, помогло некоторым. У нас также был один (очень) плохо написанный запрос на значок, который я исправил вчера, который каждый раз занимал более 20 секунд и выполнялся каждую минуту, вдобавок к этому. Я надеялся, что это было источником некоторых проблем с блокировкой!

К сожалению, я получил еще одну ошибку взаимоблокировки около 2 часов назад. Точно такие же симптомы, точно такой же виновник.

Действительно странная вещь заключается в том, что оператор SQL с блокировкой записи, который вы видите выше, является частью очень специфического пути кода. Он только выполняется при добавлении нового ответа на вопрос - он обновляет родительский вопрос новым счетчиком ответов и последней датой / пользователем. Это, очевидно, не так часто по сравнению с огромным количеством операций чтения, которые мы делаем! Насколько я могу судить, мы не выполняем огромное количество операций записи в любом месте приложения.

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

Использование NOLOCK с Linq немного сложнее, поскольку Скотт Хансельман обсуждает здесь .

Мы заигрываем с идеей использования

SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED

в базовом контексте базы данных, чтобы все наши запросы LINQ имели этот набор. Без этого нам пришлось бы обернуть все вызовы LINQ, которые мы делаем (ну, в общем, простые считывающие вызовы, а это подавляющее большинство из них), в блок кода транзакции из 3-4 строк, что ужасно.

Полагаю, я немного разочарован тем, что тривиальные операции чтения в SQL 2005 могут привести к тупиковой ситуации при записи. Я мог видеть тупики записи / записи, являющиеся огромной проблемой, но читает? У нас здесь нет банковского сайта, нам не нужна идеальная точность каждый раз.

Идеи? Мысли?


Вы создаете новый объект LINQ to SQL DataContext для каждой операции или, возможно, у вас общий статический контекст для всех ваших вызовов?

Джереми, мы разделяем один статический текст данных в основном контроллере по большей части:

private DBContext _db;
/// <summary>
/// Gets the DataContext to be used by a Request's controllers.
/// </summary>
public DBContext DB
{
    get
    {
        if (_db == null)
        {
            _db = new DBContext() { SessionName = GetType().Name };
            //_db.ExecuteCommand("SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED");
        }
        return _db;
    }
}

Рекомендуете ли мы создавать новый контекст для каждого Контроллера, или для Страницы, или ... чаще?

Ответы [ 22 ]

44 голосов
/ 22 августа 2008

По данным MSDN:

http://msdn.microsoft.com/en-us/library/ms191242.aspx

Когда либо ПРОЧИТАЙТЕ ЗАКЛЮЧЕННЫЙ или Разрешить SNAPSHOT ИЗОЛЯЦИЯ базы данных варианты включены, логические копии (версии) поддерживаются для всех данных изменения, выполненные в база данных. Каждый раз, когда строка изменяется конкретной транзакцией экземпляр хранилища компонента Database Engine версия ранее совершенного изображение строки в tempdb. каждый версия помечена транзакцией порядковый номер транзакции это сделало изменение. Версии измененные строки связаны с помощью ссылки список. Самое новое значение строки всегда хранится в текущей базе данных и прикован к версионным строкам в базе данных.

Для краткосрочных транзакций версия модифицированного ряда может получить кэшируется в пуле буферов без записывать на диск файлы база данных tempdb. Если необходимость версионный ряд недолговечен, он будет просто выпал из буферный пул и не обязательно накладные расходы на ввод / вывод.

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

Попробуйте установить эту опцию и УДАЛИТЬ все NOLOCK из запросов кода, если в этом нет особой необходимости. NOLOCK или использование глобальных методов в обработчике контекста базы данных для борьбы с уровнями изоляции транзакций базы данных являются бинтами для решения проблемы. NOLOCKS замаскирует фундаментальные проблемы с нашим слоем данных и, возможно, приведет к выбору ненадежных данных, где решением может оказаться автоматическое управление версиями выбора / обновления строк.

ALTER Database [StackOverflow.Beta] SET READ_COMMITTED_SNAPSHOT ON
37 голосов
/ 21 августа 2008

NOLOCK и READ UNCOMMITTED - скользкий уклон. Вы никогда не должны использовать их, если не понимаете, почему сначала возникает тупик. Меня беспокоит, что вы говорите: «Мы добавили (nolock) ко всем SQL-запросам». Необходимость добавления WITH NOLOCK везде является верным признаком того, что у вас есть проблемы на уровне данных.

Сам по себе оператор обновления выглядит немного проблематично. Вы определяете счет ранее в транзакции или просто извлекаете его из объекта? AnswerCount = AnswerCount+1 когда вопрос добавлен, вероятно, лучший способ справиться с этим. Тогда вам не нужна транзакция, чтобы получить правильный счет, и вам не нужно беспокоиться о проблеме параллелизма, которой вы потенциально подвержены.

Один простой способ обойти проблему взаимоблокировки этого типа без большой работы и без включения грязного чтения - это использовать "Snapshot Isolation Mode" (новое в SQL 2005), которое всегда даст вам чистое чтение последних неизмененных данных. Вы также можете легко поймать и повторить заблокированные операторы, если вы хотите обрабатывать их изящно.

25 голосов
/ 16 сентября 2008

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

Возможно, это проблема, связанная с индексом. Например, предположим, что таблица Posts имеет некластеризованный индекс X, который содержит ParentID и одно (или несколько) из обновляемых полей (AnswerCount, LastActivityDate, LastActivityUserId).

Возникла бы взаимоблокировка, если командлет SELECT выполняет блокировку общего чтения для индекса X для поиска по ParentId, а затем ему нужно сделать блокировку совместного чтения для кластеризованного индекса, чтобы получить оставшиеся столбцы, в то время как команда UPDATE выполняет исключающая запись блокировка для кластеризованного индекса, и для его обновления необходимо получить исключительную запись для индекса X.

Теперь у вас есть ситуация, когда A заблокировал X и пытается получить Y, тогда как B заблокировал Y и пытается получить X.

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

18 голосов
/ 26 августа 2008

Мне довольно неловко из-за этого вопроса и ответов на него. Там много "попробуй эту волшебную пыль! Нет этой волшебной пыли!"

Я нигде не вижу, чтобы вы проанализировали взломанные замки и определили, какой именно тип замков заблокирован.

Все, что вы указали, это то, что происходят некоторые блокировки, а не блокировка.

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

DBCC TRACEON (1222, -1)

чтобы при возникновении тупика диагностика была лучше.

14 голосов
/ 21 августа 2008

Вы создаете новый объект LINQ to SQL DataContext для каждой операции или, возможно, у вас общий статический контекст для всех ваших вызовов? Первоначально я попробовал последний подход, и, насколько я помню, это вызвало нежелательную блокировку в БД. Теперь я создаю новый контекст для каждой атомарной операции.

10 голосов
/ 22 августа 2008

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

Помните, что для тупика требуется (как минимум) 2 блокировки. Соединение 1 имеет Блокировку A, хочет Блокировку B - и наоборот для Соединения 2. Это неразрешимая ситуация, и кто-то должен дать.

То, что вы показали до сих пор, решается простой блокировкой, которую Sql Server с удовольствием сделает весь день.

Я подозреваю, что вы (или LINQ) начинаете транзакцию с этим оператором UPDATE и ВЫБИРАЕТЕ какую-то другую часть информации заранее. Но вам действительно нужно вернуться назад по графику взаимоблокировок, чтобы найти блокировки , удерживаемые каждым потоком, а затем вернуться через Profiler, чтобы найти операторы, которые привели к предоставлению этих блокировок.

Я ожидаю, что есть как минимум 4 оператора для завершения этой головоломки (или оператор, который принимает несколько блокировок - возможно, есть триггер в таблице Posts?).

7 голосов
/ 21 августа 2008

Вас волнует, устарел ли ваш профиль пользователя на несколько секунд?

Нет - это вполне приемлемо. Установка базового уровня изоляции транзакций, вероятно, является наилучшим / самым чистым способом.

4 голосов
/ 15 мая 2009

Типичная тупиковая ситуация чтения / записи возникает при доступе к порядку индекса. Read (T1) находит строку в индексе A, а затем ищет спроецированный столбец в индексе B (обычно кластеризованный). Запись (T2) изменяет индекс B (кластер), затем должен обновить индекс A. T1 имеет S-Lck на A, хочет S-Lck на B, T2 имеет X-Lck на B, хочет U-Lck на A. Deadlock , пух. Т1 убит. Это распространено в средах с большим OLTP-трафиком и просто слишком большим количеством индексов :). Решение состоит в том, чтобы заставить чтение либо не переходить с А на В (т. Е. Включить столбец в А, либо удалить столбец из прогнозируемого списка), либо Т2 не должно переходить с В на А (не обновлять индексированный столбец). К сожалению, Линк не ваш друг здесь ...

3 голосов
/ 25 августа 2008

Вы определенно хотите, чтобы READ_COMMITTED_SNAPSHOT был включен, что по умолчанию не так. Это дает вам семантику MVCC. Это то же самое, что Oracle использует по умолчанию. Наличие базы данных MVCC невероятно полезно, НЕ использовать безумно. Это позволяет вам выполнить следующую транзакцию:

Обновить ПОЛЬЗОВАТЕЛЕЙ Set FirstName = 'foobar'; // решили поспать год.

Между тем, не совершая вышесказанного, каждый может продолжать выбирать из этой таблицы просто отлично. Если вы не знакомы с MVCC, вы будете шокированы тем, что вы когда-либо могли жить без него. Шутки в сторону.

3 голосов
/ 22 августа 2008

Q. Почему вы сначала храните AnswerCount в таблице Posts?

Альтернативный подход состоит в том, чтобы исключить «обратную запись» в таблицу Posts, не сохраняя AnswerCount в таблице, а динамически вычисляя количество ответов на сообщение при необходимости.

Да, это будет означать, что вы выполняете дополнительный запрос:

SELECT COUNT(*) FROM Answers WHERE post_id = @id

или более типично (если вы отображаете это для домашней страницы):

SELECT p.post_id, 
     p.<additional post fields>,
     a.AnswerCount
FROM Posts p
    INNER JOIN AnswersCount_view a
    ON <join criteria>
WHERE <home page criteria>

, но обычно это приводит к INDEX SCAN и может быть более эффективным при использовании ресурсов, чем использование READ ISOLATION.

Существует более одного способа снять кожу с кошки. Преждевременная нормализация схемы базы данных может привести к проблемам с масштабируемостью.

...