#sql-server #multithreading
#sql-сервер #многопоточность
Вопрос:
У меня проблема с SQL-сервером, где я меняю запросы на многопоточные, и он выполняет эту хранимую процедуру при нескольких подключениях к базе данных и приводит к взаимоблокировке. Я не могу себе представить, как это приводит к взаимоблокировке. Если я делаю это однопоточным, он работает нормально. В какой-то момент (когда я впервые написал это) он работал, вставляя 22 000 записей без взаимоблокировки (таблица была пустой, поэтому никаких обновлений в этой ситуации не было) Код выглядит следующим образом:
Create Procedure InsertUpdateRecord
@Email varchar(255),
@State Varchar(50) -- Other parameters, shouldn't matter
as
Begin
if (not exists (select * from [MyTable] WITH (NOLOCK) where email=@email))
begin
insert into [MyTable] (email, state) values (@email, @state)
end
else
begin
update [MyTable] set state=@state where email=@email
end
end
end
Довольно просто, и я не вижу никакого способа, которым это могло бы быть сделано, кроме простой блокировки строк в уникальном столбце «email», и я не вижу никакого способа, которым это могло бы привести к тупиковой ситуации. Может кто-нибудь объяснить это и предложить решение?
Комментарии:
1. Можете ли вы предоставить нам план выполнения и / или дополнительную информацию об индексах в [MyTable], если таковые имеются?
Ответ №1:
Измените эту строку кода с
if exists (select * from table where key = @key)
Для
if exists (select 1 from table where key = @key)
Это более эффективно
Также следуйте этим распространенным методам, которые помогают минимизировать взаимоблокировки: доступ к объектам в том же порядке. Избегайте взаимодействия с пользователем в транзакциях. Держите транзакции короткими и в одном пакете. Используйте более низкий уровень изоляции. Используйте уровень изоляции на основе управления версиями строк. Используйте связанные соединения.
Как подробно описано здесь: минимизировать взаимоблокировки
Комментарии:
1. Это на самом деле — больше нет? — верно: blog.sqlauthority.com/2008/02/26/… Это то, что мне всегда говорили и в прошлом, но, по-видимому, ложно.
2. Это его последние слова в блоге «Я использовал SELECT 1 вместо SELECT * при проверке наличия строк. Я хотел бы узнать мнение моих читателей по этому поводу. Пожалуйста, выскажите свое мнение и оставьте свой комментарий здесь. »
3. Просто для ясности, я тоже. Но, похоже, эффект в лучшем случае очень незначительный. Я был бы ОЧЕНЬ удивлен, если бы это стало причиной его взаимоблокировки.
4. Я этого не говорил. Но когда происходит взаимоблокировка, любое улучшение, которое вы можете внести в производительность, полезно.
5. С точки зрения доступа к объектам в том же порядке, что вы видите, это то, что вы получаете: я обращаюсь только к одной таблице. Выберите 1 против Select * — не имеет значения, извлекаю ли я данные из строки или использую 1, но отмечен для производительности, вообще не используя транзакции (поможет ли это ??) Какой уровень изоляции ниже, чем NOLOCK? Связанные соединения могут работать, но кажутся огромным излишеством и в дальнейшем усложнят поддержку кода
Ответ №2:
Это типичный случай, который вы должны учитывать UPDLOCK
в своем запросе:
begin tran
if exists (select * from table with (updlock,serializable) where key = @key)
begin
update table set ...
where key = @key
end
else
begin
insert into table (key, ...)
values (@key, ...)
end
commit tran
Уже UPDLOCK
будет требовать блокировки, если вы нажмете на свою запись с помощью первого выбора и избежите случая, когда несколько методов пытаются получить одну и ту же блокировку.
Используется serializable
для:
- Операторы не могут считывать данные, которые были изменены, но еще не зафиксированы другими транзакциями.
- Никакие другие транзакции не могут изменять данные, которые были прочитаны текущей транзакцией, пока текущая транзакция не завершится.
- Другие транзакции не могут вставлять новые строки со значениями ключей, которые попадают в диапазон ключей, считываемых любыми операторами в текущей транзакции, до завершения текущей транзакции.
Просто для полноты, ваша таблица правильно проиндексирована и не является кучей, верно?
Кроме того, если нет, попробуйте добавить индекс в свой MyTable
:
CREATE NONCLUSTERED INDEX [IX_MyTable_email] ON [dbo].[MyTable]
(
[email] ASC
)
Вы также можете продолжить и включить следующие флаги трассировки:
DBCC TRACEON (1204,-1)
DBCC TRACEON (1222,-1)
Это не остановит взаимоблокировки, но предоставит более подробную информацию о них в вашем журнале SQL Server. Он сообщит вам, какие блокировки пытаются использовать ваши транзакции, и кто в настоящее время владеет этой блокировкой.
Комментарии:
1. Попробовал это изменение, все еще получая взаимоблокировки. Я не понимаю, как вообще возможно получить взаимоблокировку в этой ситуации, оба соединения запрашивают любые блокировки в одном и том же порядке, поэтому невозможно создать взаимоблокировку, если SQL не делает что-то действительно сумасшедшее. Может кто-нибудь объяснить, какие блокировки создаются для выбора (строка?), обновления (строка?) и вставки (строка?) — все это будут уникальные строки, поэтому конфликтов не будет. Если бы это были все блокировки таблиц, по-прежнему не было взаимоблокировок, поскольку все блокируют таблицы.
2. У вас есть индексы в вашей таблице?
3. @TraderhutGames обновил мой ответ. Пожалуйста, предоставьте более подробную информацию о рассматриваемой таблице, но если в электронной почте нет индекса, это может быть хорошим способом начать.
4. Похоже, что в электронном письме отсутствовал индекс. (Были две таблицы, которые терпели неудачу, в обеих из которых отсутствовал индекс. Я исправил неправильный и повторно протестировал:- (Итак, теперь это имеет некоторый смысл, я предполагаю, что он везде блокировал таблицы, но это все еще не объясняет мне, как это могло привести к взаимоблокировке.)
5. Добавление индекса уменьшило вероятность взаимоблокировки до ОГРОМНОГО уровня, вместо того, чтобы получить ее в пределах первых 90 записей, я добрался до 270 000, прежде чем она зашла в тупик. (Это имеет смысл, поскольку if () выполняется намного быстрее без необходимости выполнять полное сканирование таблицы) Но, к сожалению, я пока не могу использовать это решение, поскольку я все еще захожу в тупик. Я попробую трассировку, чтобы увидеть, что это дает. Где мне получить результаты этого?