В базе данных 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;
}
}
Вы рекомендуете создавать новый контекст для каждого контроллера, или для каждой страницы, или… чаще?
источник
Ответы:
Согласно MSDN:
http://msdn.microsoft.com/en-us/library/ms191242.aspx
Похоже, что из-за дополнительных накладных расходов производительность немного снижается, но может быть незначительной. Мы должны проверить, чтобы убедиться.
Попробуйте установить эту опцию и УДАЛИТЬ все NOLOCK из запросов кода, если это действительно не необходимо. NOLOCK или использование глобальных методов в обработчике контекста базы данных для борьбы с уровнями изоляции транзакций базы данных - это повязки на проблему. NOLOCKS замаскирует фундаментальные проблемы с нашим уровнем данных и, возможно, приведет к выбору ненадежных данных, где автоматическое управление версиями строк выбора / обновления кажется решением.
ALTER Database [StackOverflow.Beta] SET READ_COMMITTED_SNAPSHOT ON
источник
NOLOCK и READ UNCOMMITTED - скользкая дорожка . Вы никогда не должны использовать их, если не понимаете, почему сначала возникает тупик. Меня беспокоит, что вы скажете: «Мы добавили с (nolock) ко всем запросам SQL». Необходимость везде добавлять WITH NOLOCK - верный признак того, что у вас есть проблемы на уровне данных.
Сама инструкция обновления выглядит немного проблематичной. Вы определяете счетчик раньше в транзакции или просто берете его из объекта?
AnswerCount = AnswerCount+1
когда вопрос добавлен, вероятно, лучший способ справиться с этим. Тогда вам не нужна транзакция, чтобы получить правильный счет, и вам не нужно беспокоиться о проблеме параллелизма, которой вы потенциально можете подвергнуть себя.Один из простых способов обойти проблему взаимоблокировки этого типа без большой работы и без включения грязного чтения - это использовать
"Snapshot Isolation Mode"
(новый в SQL 2005), который всегда дает вам чистое чтение последних немодифицированных данных. Вы также можете довольно легко перехватить и повторить тупиковую инструкцию, если хотите изящно с ними обращаться.источник
Вопрос OP заключался в том, чтобы спросить, почему возникла эта проблема. Этот пост надеется ответить на этот вопрос, оставляя возможные решения на усмотрение других.
Вероятно, это проблема, связанная с индексом. Например, допустим, что таблица Posts имеет некластеризованный индекс X, который содержит ParentID и одно (или несколько) обновляемых полей (AnswerCount, LastActivityDate, LastActivityUserId).
Взаимоблокировка может произойти, если команда SELECT выполняет блокировку совместного чтения по индексу X для поиска по ParentId, а затем ему необходимо выполнить блокировку общего чтения в кластеризованном индексе, чтобы получить оставшиеся столбцы, в то время как команда UPDATE выполняет исключительную запись заблокировать кластеризованный индекс и получить монопольную блокировку записи для индекса X, чтобы обновить его.
Теперь у вас есть ситуация, когда A заблокировал X и пытается получить Y, тогда как B заблокировал Y и пытается получить X.
Конечно, нам понадобится OP, чтобы обновить свою публикацию дополнительной информацией о том, какие индексы используются, чтобы подтвердить, действительно ли это является причиной.
источник
Мне довольно не нравится этот вопрос и сопутствующие ответы. Есть много фраз: «Попробуйте эту волшебную пыль! Нет, эту волшебную пыль!»
Я нигде не вижу, чтобы вы проанализировали взятые блокировки и определили, какие именно блокировки заблокированы.
Все, что вы указали, - это то, что происходят некоторые блокировки, а не тупиковые.
В SQL 2005 вы можете получить больше информации о том, какие блокировки снимаются, используя:
так что когда возникает тупик, у вас будет лучшая диагностика.
источник
Вы создаете экземпляр нового объекта LINQ to SQL DataContext для каждой операции или, возможно, используете один и тот же статический контекст для всех ваших вызовов? Первоначально я пробовал второй подход, и, насколько я помню, он вызвал нежелательную блокировку БД. Теперь я создаю новый контекст для каждой атомарной операции.
источник
Прежде чем сжечь дом, чтобы поймать муху с помощью NOLOCK повсюду, вы можете взглянуть на график тупиков, который вы должны были захватить с помощью Profiler.
Помните, что для тупиковой ситуации требуется (как минимум) 2 блокировки. Соединение 1 имеет блокировку A, хочет блокировку B - и наоборот для соединения 2. Это неразрешимая ситуация, и кто-то должен уступить.
То, что вы показали до сих пор, решается простой блокировкой, которую Sql Server с радостью делает в течение всего дня.
Я подозреваю, что вы (или LINQ) начинаете транзакцию с этим оператором UPDATE и заранее ВЫБИРАЕТЕ какую-то другую информацию. Но вам действительно нужно пройти через график тупиковых ситуаций, чтобы найти блокировки, удерживаемые каждым потоком, а затем вернуться через Profiler, чтобы найти операторы, которые вызвали эти блокировки.
Я ожидаю, что для завершения этой головоломки есть как минимум 4 оператора (или оператор, который требует нескольких блокировок - возможно, в таблице сообщений есть триггер?).
источник
Нет - это вполне приемлемо. Установка базового уровня изоляции транзакции, вероятно, является лучшим / самым чистым способом.
источник
Типичная взаимоблокировка чтения / записи возникает из-за доступа по порядку индекса. Чтение (T1) находит строку по индексу A, а затем ищет спроецированный столбец по индексу B (обычно кластеризованный). Запись (T2) изменяет индекс B (кластер), а затем обновляет индекс A. T1 имеет S-Lck на A, хочет S-Lck на B, T2 имеет X-Lck на B, хочет U-Lck на A. Тупик , затяжка. T1 убит. Это распространено в средах с интенсивным OLTP-трафиком и слишком большим количеством индексов :). Решение состоит в том, чтобы либо при чтении не переходить от A к B (т.е. включенный столбец в A, либо удалять столбец из спроецированного списка), либо T2 не должен переходить от B к A (не обновлять индексированный столбец). К сожалению, linq здесь вам не друг ...
источник
@Jeff - Я определенно не эксперт в этом, но я добился хороших результатов с созданием нового контекста почти при каждом вызове. Я думаю, что это похоже на создание нового объекта Connection при каждом вызове ADO. Накладные расходы не так велики, как вы думаете, поскольку пул соединений все равно будет использоваться.
Я просто использую глобальный статический помощник вроде этого:
а потом делаю что-то вроде этого:
var db = AppData.DB; var results = from p in db.Posts where p.ID = id select p;
И я бы сделал то же самое с обновлениями. В любом случае, у меня не так много трафика, как у вас, но я определенно получал некоторую блокировку, когда на раннем этапе использовал общий DataContext с небольшим количеством пользователей. Никаких гарантий, но, возможно, стоит попробовать.
Обновить : опять же, глядя на свой код, вы используете только контекст данных на время жизни этого конкретного экземпляра контроллера, что в основном кажется прекрасным, если только он каким-то образом не используется одновременно несколькими вызовами внутри контроллера. В ветке по этой теме ScottGu сказал:
Так или иначе, это может быть не так, но опять же, вероятно, стоит попробовать, возможно, в сочетании с некоторым нагрузочным тестированием.
источник
Вопрос : Почему вы хранение
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
.Есть несколько способов снять шкуру с кошки. Преждевременная денормализация схемы базы данных может вызвать проблемы с масштабируемостью.
источник
Вы определенно хотите, чтобы READ_COMMITTED_SNAPSHOT был включен, а по умолчанию это не так. Это дает вам семантику MVCC. Это то же самое, что Oracle использует по умолчанию. Наличие базы данных MVCC настолько невероятно полезно, что НЕ использовать ее - безумие. Это позволяет вам запускать внутри транзакции следующее:
Обновить ПОЛЬЗОВАТЕЛИ Установить FirstName = 'foobar'; // решаем поспать год.
тем временем, не выполняя вышеуказанного, каждый может продолжать выбирать из этой таблицы. Если вы не знакомы с MVCC, вы будете шокированы тем, что когда-либо могли жить без него. Шутки в сторону.
источник
Установка по умолчанию на чтение незафиксированных - не лучшая идея. Вы, несомненно, внесете несоответствия и в конечном итоге столкнетесь с проблемой, которая хуже той, что у вас есть сейчас. Изоляция снимков может хорошо работать, но это резкое изменение пути Sql Server работает и ставит огромные нагрузку на базу данных tempdb.
Вот что вам следует сделать: используйте try-catch (в T-SQL) для обнаружения состояния взаимоблокировки. Когда это произойдет, просто запустите запрос повторно. Это стандартная практика программирования баз данных.
Хорошие примеры этой техники можно найти в Библии Пола Нильсона по Sql Server 2005 .
Вот быстрый шаблон, который я использую:
-- Deadlock retry template declare @lastError int; declare @numErrors int; set @numErrors = 0; LockTimeoutRetry: begin try; -- The query goes here return; -- this is the normal end of the procedure end try begin catch set @lastError=@@error if @lastError = 1222 or @lastError = 1205 -- Lock timeout or deadlock begin; if @numErrors >= 3 -- We hit the retry limit begin; raiserror('Could not get a lock after 3 attempts', 16, 1); return -100; end; -- Wait and then try the transaction again waitfor delay '00:00:00.25'; set @numErrors = @numErrors + 1; goto LockTimeoutRetry; end; -- Some other error occurred declare @errorMessage nvarchar(4000), @errorSeverity int select @errorMessage = error_message(), @errorSeverity = error_severity() raiserror(@errorMessage, @errorSeverity, 1) return -100 end catch;
источник
Одна вещь, которая работала для меня в прошлом, - убедиться, что все мои запросы и обновления обращаются к ресурсам (таблицам) в одном и том же порядке.
То есть, если один запрос обновляется в порядке Table1, Table2, а другой запрос обновляет его в порядке Table2, Table1, тогда вы можете увидеть взаимоблокировки.
Не уверен, можно ли изменить порядок обновлений, так как вы используете LINQ. Но посмотреть есть на что.
источник
Несколько секунд определенно было бы приемлемо. В любом случае, не похоже, что это будет так долго, если только огромное количество людей не отправят ответы одновременно.
источник
Я согласен с Джереми в этом. Вы спрашиваете, следует ли вам создавать новый контекст данных для каждого контроллера или для каждой страницы - я обычно создаю новый для каждого независимого запроса.
В настоящее время я создаю решение, которое использовалось для реализации статического контекста, как и вы, и когда я бросал тонны запросов на чудовище сервера (более миллиона) во время стресс-тестов, я также случайным образом получал блокировки чтения / записи.
Как только я изменил свою стратегию на использование другого контекста данных на уровне LINQ для каждого запроса и поверил, что SQL-сервер может использовать свою магию пула соединений, блокировки, казалось, исчезли.
Конечно, у меня был некоторый дефицит времени, поэтому я пробовал несколько вещей одновременно, поэтому я не могу быть на 100% уверен, что именно это исправило, но у меня высокий уровень уверенности - давайте скажем так .
источник
Вы должны реализовать грязное чтение.
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
Если вам не требуется абсолютная целостность транзакций для ваших запросов, вы должны использовать грязное чтение при доступе к таблицам с высоким уровнем параллелизма. Я предполагаю, что ваша таблица сообщений будет одной из таких.
Это может дать вам так называемое «фантомное чтение», когда ваш запрос воздействует на данные из транзакции, которая не была зафиксирована.
Используйте грязное чтение. Вы правы в том, что они не дадут вам идеальной точности, но они должны решить ваши проблемы с мертвой блокировкой.
Если вы реализуете грязное чтение в «контексте базовой базы данных», вы всегда можете обернуть свои индивидуальные вызовы, используя более высокий уровень изоляции, если вам нужна транзакционная целостность.
источник
Так в чем же проблема с реализацией механизма повтора? Всегда будет возможность возникновения тупика, так почему бы не иметь логики, чтобы определить его и просто попробовать еще раз?
Разве по крайней мере некоторые из других вариантов не будут вводить штрафы за производительность, которые применяются постоянно, когда система повторных попыток срабатывает редко?
Кроме того, не забывайте вести журнал, когда повторяется попытка, чтобы не попасть в ситуацию, которая редко случается часто.
источник
Теперь, когда я вижу ответ Джереми, мне кажется, я помню, что слышал, что лучше всего использовать новый DataContext для каждой операции с данными. Роб Конери написал несколько сообщений о DataContext, и он всегда сообщает о них, а не использует синглтон.
Вот шаблон, который мы использовали для Video.Show ( ссылка на исходный код в CodePlex ):
Затем на уровне обслуживания (или даже более детально, для обновлений):
private VideoShowDataContext dataContext = DataContextFactory.DataContext(); public VideoSearchResult GetVideos(int pageSize, int pageNumber, string sortType) { var videos = from video in DataContext.Videos where video.StatusId == (int)VideoServices.VideoStatus.Complete orderby video.DatePublished descending select video; return GetSearchResult(videos, pageSize, pageNumber); }
источник
Я должен согласиться с Грегом, если установка уровня изоляции на чтение незафиксированных не оказывает вредного воздействия на другие запросы.
Мне было бы интересно узнать, Джефф, как его установка на уровне базы данных повлияет на такой запрос, как следующий:
Begin Tran Insert into Table (Columns) Values (Values) Select Max(ID) From Table Commit Tran
источник
Меня устраивает, если мой профиль даже на несколько минут устарел.
Вы повторно пытаетесь прочитать после сбоя? Это, безусловно, возможно при запуске тонны случайных чтений, что некоторые из них попадут, когда не могут читать. В большинстве приложений, с которыми я работаю, очень мало операций записи по сравнению с количеством операций чтения, и я уверен, что количество операций чтения далеко от того, которое вы получаете.
Если реализация «READ UNCOMMITTED» не решает вашу проблему, тогда трудно помочь, не зная больше об обработке. Может быть другая настройка, которая поможет в таком поведении. Если на помощь не придет какой-нибудь гуру MSSQL, я рекомендую передать проблему поставщику.
источник
Я бы и дальше все настраивал; как работает дисковая подсистема? Какова средняя длина дисковой очереди? Если выполняется резервное копирование операций ввода-вывода, реальной проблемой могут быть не эти два запроса, которые находятся в тупике, это может быть другой запрос, который является узким местом системы; вы упомянули настроенный запрос, занимающий 20 секунд, есть ли другие?
Сосредоточьтесь на сокращении длительных запросов, я уверен, что проблемы с тупиками исчезнут.
источник
Была та же проблема, и я не мог использовать «IsolationLevel = IsolationLevel.ReadUncommitted» в TransactionScope, потому что на сервере не включен DTS (!).
Вот что я сделал с методом расширения:
public static void SetNoLock(this MyDataContext myDS) { myDS.ExecuteCommand("SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED"); }
Итак, для выбранных, использующих критические таблицы параллелизма, мы включаем блокировку следующим образом:
Предложения приветствуются!
источник